Ruby on Rails | Screencasts | Download | Documentation | Weblog | Community | Source
Show
Ignore:
Timestamp:
09/26/07 01:24:07 (1 year ago)
Author:
bitsweat
Message:

Move Railties' Dispatcher to ActionController::Dispatcher, introduce before_ and after_dispatch callbacks, and warm up to non-CGI requests.

Files:

Legend:

Unmodified
Added
Removed
Modified
Copied
Moved
  • trunk/railties/lib/dispatcher.rb

    r7626 r7640  
    2121# WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 
    2222#++ 
    23  
    24 # This class provides an interface for dispatching a CGI (or CGI-like) request 
    25 # to the appropriate controller and action. It also takes care of resetting 
    26 # the environment (when Dependencies.load? is true) after each request. 
    27 class Dispatcher 
    28   class << self 
    29     # Dispatch the given CGI request, using the given session options, and 
    30     # emitting the output via the given output.  If you dispatch with your 
    31     # own CGI object be sure to handle the exceptions it raises on multipart 
    32     # requests (EOFError and ArgumentError). 
    33     def dispatch(cgi = nil, session_options = ActionController::CgiRequest::DEFAULT_SESSION_OPTIONS, output = $stdout) 
    34       controller = nil 
    35       if cgi ||= new_cgi(output) 
    36         request, response = ActionController::CgiRequest.new(cgi, session_options), ActionController::CgiResponse.new(cgi) 
    37         prepare_application 
    38         controller = ActionController::Routing::Routes.recognize(request) 
    39         controller.process(request, response).out(output) 
    40       end 
    41     rescue Exception => exception # errors from CGI dispatch 
    42       failsafe_response(cgi, output, '500 Internal Server Error', exception) do 
    43         controller ||= (ApplicationController rescue ActionController::Base) 
    44         controller.process_with_exception(request, response, exception).out(output) 
    45       end 
    46     ensure 
    47       # Do not give a failsafe response here 
    48       flush_logger 
    49       reset_after_dispatch 
    50     end 
    51  
    52     # Reset the application by clearing out loaded controllers, views, actions, 
    53     # mailers, and so forth. This allows them to be loaded again without having 
    54     # to restart the server (WEBrick, FastCGI, etc.). 
    55     def reset_application! 
    56       ActiveRecord::Base.reset_subclasses if defined?(ActiveRecord) 
    57  
    58       Dependencies.clear 
    59  
    60       ActiveRecord::Base.clear_reloadable_connections! if defined?(ActiveRecord) 
    61     end 
    62  
    63     # Add a preparation callback. Preparation callbacks are run before every 
    64     # request in development mode, and before the first request in production 
    65     # mode. 
    66     #  
    67     # An optional identifier may be supplied for the callback. If provided, 
    68     # to_prepare may be called again with the same identifier to replace the 
    69     # existing callback. Passing an identifier is a suggested practice if the 
    70     # code adding a preparation block may be reloaded. 
    71     def to_prepare(identifier = nil, &block) 
    72       unless identifier.nil? 
    73         callback = preparation_callbacks.detect { |ident, _| ident == identifier } 
    74  
    75         if callback # Already registered: update the existing callback 
    76           callback[-1] = block 
    77           return 
    78         end 
    79       end 
    80  
    81       preparation_callbacks << [identifier, block] 
    82  
    83       return 
    84     end 
    85  
    86     private 
    87       attr_accessor_with_default :preparation_callbacks, [] 
    88       attr_accessor_with_default :preparation_callbacks_run, false 
    89  
    90       # CGI.new plus exception handling.  CGI#read_multipart raises EOFError 
    91       # if body.empty? or body.size != Content-Length and raises ArgumentError 
    92       # if Content-Length is non-integer. 
    93       def new_cgi(output) 
    94         failsafe_response(nil, output, '400 Bad Request') { CGI.new } 
    95       end 
    96  
    97       def prepare_application 
    98         if Dependencies.load? 
    99           ActionController::Routing::Routes.reload 
    100           self.preparation_callbacks_run = false 
    101         end 
    102  
    103         require_dependency 'application' unless Object.const_defined?(:ApplicationController) 
    104         ActiveRecord::Base.verify_active_connections! if defined?(ActiveRecord) 
    105         run_preparation_callbacks 
    106       end 
    107  
    108       def reset_after_dispatch 
    109         reset_application! if Dependencies.load? 
    110       end 
    111  
    112       def run_preparation_callbacks 
    113         return if preparation_callbacks_run 
    114         preparation_callbacks.each { |_, callback| callback.call } 
    115         self.preparation_callbacks_run = true 
    116       end 
    117  
    118       # If the block raises, send status code as a last-ditch response. 
    119       def failsafe_response(cgi, fallback_output, status, exception = nil) 
    120         yield 
    121       rescue Exception 
    122         begin 
    123           log_failsafe_exception(cgi, status, exception) 
    124  
    125           body = failsafe_response_body(status) 
    126           if cgi 
    127             head = { 'status' => status, 'type' => 'text/html' } 
    128  
    129             # FIXME: using CGI differently than CGIResponse does breaks 
    130             # the Mongrel CGI wrapper. 
    131             if defined?(Mongrel) && cgi.is_a?(Mongrel::CGIWrapper) 
    132               # FIXME: set a dummy cookie so the Mongrel CGI wrapper will 
    133               # also consider @output_cookies (used for session cookies.) 
    134               head['cookie'] = [] 
    135               cgi.header(head) 
    136               fallback_output << body 
    137             else 
    138               cgi.out(head) { body } 
    139             end 
    140           else 
    141             fallback_output.write "Status: #{status}\r\nContent-Type: text/html\r\n\r\n#{body}" 
    142           end 
    143           nil 
    144         rescue Exception  # Logger or IO errors 
    145         end 
    146       end 
    147  
    148       def failsafe_response_body(status) 
    149         error_path = "#{RAILS_ROOT}/public/#{status[0..3]}.html" 
    150  
    151         if File.exists?(error_path) 
    152           File.read(error_path) 
    153         else 
    154           "<html><body><h1>#{status}</h1></body></html>" 
    155         end 
    156       end 
    157  
    158       def log_failsafe_exception(cgi, status, exception) 
    159         fell_back = cgi ? 'has cgi' : 'no cgi, fallback ouput' 
    160         message = "DISPATCHER FAILSAFE RESPONSE (#{fell_back}) #{Time.now}\n  Status: #{status}\n" 
    161         message << "  #{exception}\n    #{exception.backtrace.join("\n    ")}" if exception 
    162         failsafe_logger.fatal message 
    163       end 
    164  
    165       def failsafe_logger 
    166         if defined?(RAILS_DEFAULT_LOGGER) && !RAILS_DEFAULT_LOGGER.nil? 
    167           RAILS_DEFAULT_LOGGER 
    168         else 
    169           ActiveSupport::BufferedLogger.new($stderr) 
    170         end 
    171       end 
    172        
    173       def flush_logger 
    174         RAILS_DEFAULT_LOGGER.flush if defined?(RAILS_DEFAULT_LOGGER) && RAILS_DEFAULT_LOGGER.respond_to?(:flush) 
    175       end 
    176   end 
    177 end 
    178  
    179 Dispatcher.to_prepare :activerecord_instantiate_observers do 
    180   ActiveRecord::Base.instantiate_observers 
    181 end if defined?(ActiveRecord) 
     23require 'action_controller/dispatcher' 
     24Dispatcher = ActionController::Dispatcher