Ruby on Rails | Screencasts | Download | Documentation | Weblog | Community | Source

root/trunk/actionpack/lib/action_controller/dispatcher.rb

Revision 9055, 5.9 kB (checked in by bitsweat, 9 months ago)

Refactor filters to use Active Support callbacks. Closes #11235.

Line 
1 module ActionController
2   # Dispatches requests to the appropriate controller and takes care of
3   # reloading the app after each request when Dependencies.load? is true.
4   class Dispatcher
5     @@guard = Mutex.new
6
7     class << self
8       # Backward-compatible class method takes CGI-specific args. Deprecated
9       # in favor of Dispatcher.new(output, request, response).dispatch.
10       def dispatch(cgi = nil, session_options = CgiRequest::DEFAULT_SESSION_OPTIONS, output = $stdout)
11         new(output).dispatch_cgi(cgi, session_options)
12       end
13
14       # Add a preparation callback. Preparation callbacks are run before every
15       # request in development mode, and before the first request in production
16       # mode.
17       #
18       # An optional identifier may be supplied for the callback. If provided,
19       # to_prepare may be called again with the same identifier to replace the
20       # existing callback. Passing an identifier is a suggested practice if the
21       # code adding a preparation block may be reloaded.
22       def to_prepare(identifier = nil, &block)
23         @prepare_dispatch_callbacks ||= ActiveSupport::Callbacks::CallbackChain.new
24         callback = ActiveSupport::Callbacks::Callback.new(:prepare_dispatch, block, :identifier => identifier)
25         @prepare_dispatch_callbacks.replace_or_append_callback(callback)
26       end
27
28       # If the block raises, send status code as a last-ditch response.
29       def failsafe_response(fallback_output, status, originating_exception = nil)
30         yield
31       rescue Exception => exception
32         begin
33           log_failsafe_exception(status, originating_exception || exception)
34           body = failsafe_response_body(status)
35           fallback_output.write "Status: #{status}\r\nContent-Type: text/html\r\n\r\n#{body}"
36           nil
37         rescue Exception => failsafe_error # Logger or IO errors
38           $stderr.puts "Error during failsafe response: #{failsafe_error}"
39           $stderr.puts "(originally #{originating_exception})" if originating_exception
40         end
41       end
42
43       private
44         def failsafe_response_body(status)
45           error_path = "#{error_file_path}/#{status.to_s[0..3]}.html"
46
47           if File.exist?(error_path)
48             File.read(error_path)
49           else
50             "<html><body><h1>#{status}</h1></body></html>"
51           end
52         end
53
54         def log_failsafe_exception(status, exception)
55           message = "/!\\ FAILSAFE /!\\  #{Time.now}\n  Status: #{status}\n"
56           message << "  #{exception}\n    #{exception.backtrace.join("\n    ")}" if exception
57           failsafe_logger.fatal message
58         end
59
60         def failsafe_logger
61           if defined?(::RAILS_DEFAULT_LOGGER) && !::RAILS_DEFAULT_LOGGER.nil?
62             ::RAILS_DEFAULT_LOGGER
63           else
64             Logger.new($stderr)
65           end
66         end
67     end
68
69     cattr_accessor :error_file_path
70     self.error_file_path = "#{::RAILS_ROOT}/public" if defined? ::RAILS_ROOT
71
72     cattr_accessor :unprepared
73     self.unprepared = true
74
75     include ActiveSupport::Callbacks
76     define_callbacks :prepare_dispatch, :before_dispatch, :after_dispatch
77
78     before_dispatch :reload_application
79     before_dispatch :prepare_application
80     after_dispatch :flush_logger
81     after_dispatch :cleanup_application
82
83     if defined? ActiveRecord
84       to_prepare :activerecord_instantiate_observers do
85         ActiveRecord::Base.instantiate_observers
86       end
87     end
88
89     def initialize(output, request = nil, response = nil)
90       @output, @request, @response = output, request, response
91     end
92
93     def dispatch
94       @@guard.synchronize do
95         begin
96           run_callbacks :before_dispatch
97           handle_request
98         rescue Exception => exception
99           failsafe_rescue exception
100         ensure
101           run_callbacks :after_dispatch, :enumerator => :reverse_each
102         end
103       end
104     end
105
106     def dispatch_cgi(cgi, session_options)
107       if cgi ||= self.class.failsafe_response(@output, '400 Bad Request') { CGI.new }
108         @request = CgiRequest.new(cgi, session_options)
109         @response = CgiResponse.new(cgi)
110         dispatch
111       end
112     rescue Exception => exception
113       failsafe_rescue exception
114     end
115
116     def reload_application
117       if Dependencies.load?
118         Routing::Routes.reload
119         self.unprepared = true
120       end
121     end
122
123     def prepare_application(force = false)
124       begin
125         require_dependency 'application' unless defined?(::ApplicationController)
126       rescue LoadError => error
127         raise unless error.message =~ /application\.rb/
128       end
129
130       ActiveRecord::Base.verify_active_connections! if defined?(ActiveRecord)
131
132       if unprepared || force
133         run_callbacks :prepare_dispatch
134         ActionView::TemplateFinder.reload! unless ActionView::Base.cache_template_loading
135         self.unprepared = false
136       end
137     end
138
139     # Cleanup the application by clearing out loaded classes so they can
140     # be reloaded on the next request without restarting the server.
141     def cleanup_application(force = false)
142       if Dependencies.load? || force
143         ActiveRecord::Base.reset_subclasses if defined?(ActiveRecord)
144         Dependencies.clear
145         ActiveRecord::Base.clear_reloadable_connections! if defined?(ActiveRecord)
146       end
147     end
148
149     def flush_logger
150       RAILS_DEFAULT_LOGGER.flush if defined?(RAILS_DEFAULT_LOGGER) && RAILS_DEFAULT_LOGGER.respond_to?(:flush)
151     end
152
153     protected
154       def handle_request
155         @controller = Routing::Routes.recognize(@request)
156         @controller.process(@request, @response).out(@output)
157       end
158
159       def failsafe_rescue(exception)
160         self.class.failsafe_response(@output, '500 Internal Server Error', exception) do
161           if @controller ||= defined?(::ApplicationController) ? ::ApplicationController : Base
162             @controller.process_with_exception(@request, @response, exception).out(@output)
163           else
164             raise exception
165           end
166         end
167       end
168   end
169 end
Note: See TracBrowser for help on using the browser.