diff options
Diffstat (limited to 'actionpack/lib/action_dispatch/middleware')
18 files changed, 367 insertions, 104 deletions
diff --git a/actionpack/lib/action_dispatch/middleware/callbacks.rb b/actionpack/lib/action_dispatch/middleware/callbacks.rb new file mode 100644 index 0000000000..0a2b4cf5f7 --- /dev/null +++ b/actionpack/lib/action_dispatch/middleware/callbacks.rb @@ -0,0 +1,40 @@ +module ActionDispatch + class Callbacks + include ActiveSupport::Callbacks + define_callbacks :prepare, :before, :after + + class << self + # DEPRECATED + alias_method :prepare_dispatch, :prepare + alias_method :before_dispatch, :before + alias_method :after_dispatch, :after + end + + # Add a preparation callback. Preparation callbacks are run before every + # request in development mode, and before the first request in production + # mode. + # + # An optional identifier may be supplied for the callback. If provided, + # to_prepare may be called again with the same identifier to replace the + # existing callback. Passing an identifier is a suggested practice if the + # code adding a preparation block may be reloaded. + def self.to_prepare(identifier = nil, &block) + @prepare_callbacks ||= ActiveSupport::Callbacks::CallbackChain.new + callback = ActiveSupport::Callbacks::Callback.new(:prepare, block, :identifier => identifier) + @prepare_callbacks.replace_or_append!(callback) + end + + def initialize(app, prepare_each_request = false) + @app, @prepare_each_request = app, prepare_each_request + run_callbacks :prepare + end + + def call(env) + run_callbacks :before + run_callbacks :prepare if @prepare_each_request + @app.call(env) + ensure + run_callbacks :after, :enumerator => :reverse_each + end + end +end diff --git a/actionpack/lib/action_dispatch/middleware/failsafe.rb b/actionpack/lib/action_dispatch/middleware/failsafe.rb deleted file mode 100644 index 7379a696aa..0000000000 --- a/actionpack/lib/action_dispatch/middleware/failsafe.rb +++ /dev/null @@ -1,52 +0,0 @@ -module ActionDispatch - class Failsafe - cattr_accessor :error_file_path - self.error_file_path = Rails.public_path if defined?(Rails.public_path) - - def initialize(app) - @app = app - end - - def call(env) - @app.call(env) - rescue Exception => exception - # Reraise exception in test environment - if env["rack.test"] - raise exception - else - failsafe_response(exception) - end - end - - private - def failsafe_response(exception) - log_failsafe_exception(exception) - [500, {'Content-Type' => 'text/html'}, failsafe_response_body] - rescue Exception => failsafe_error # Logger or IO errors - $stderr.puts "Error during failsafe response: #{failsafe_error}" - end - - def failsafe_response_body - error_path = "#{self.class.error_file_path}/500.html" - if File.exist?(error_path) - File.read(error_path) - else - "<html><body><h1>500 Internal Server Error</h1></body></html>" - end - end - - def log_failsafe_exception(exception) - message = "/!\\ FAILSAFE /!\\ #{Time.now}\n Status: 500 Internal Server Error\n" - message << " #{exception}\n #{exception.backtrace.join("\n ")}" if exception - failsafe_logger.fatal(message) - end - - def failsafe_logger - if defined?(Rails) && Rails.logger - Rails.logger - else - Logger.new($stderr) - end - end - end -end diff --git a/actionpack/lib/action_dispatch/middleware/params_parser.rb b/actionpack/lib/action_dispatch/middleware/params_parser.rb index 6df572268c..e83cf9236b 100644 --- a/actionpack/lib/action_dispatch/middleware/params_parser.rb +++ b/actionpack/lib/action_dispatch/middleware/params_parser.rb @@ -1,3 +1,5 @@ +require 'active_support/json' + module ActionDispatch class ParamsParser ActionController::Base.param_parsers[Mime::XML] = :xml_simple @@ -9,7 +11,7 @@ module ActionDispatch def call(env) if params = parse_formatted_parameters(env) - env["action_controller.request.request_parameters"] = params + env["action_dispatch.request.request_parameters"] = params end @app.call(env) @@ -30,16 +32,14 @@ module ActionDispatch when Proc strategy.call(request.raw_post) when :xml_simple, :xml_node - body = request.raw_post - body.blank? ? {} : Hash.from_xml(body).with_indifferent_access + request.body.size == 0 ? {} : Hash.from_xml(request.body).with_indifferent_access when :yaml - YAML.load(request.raw_post) + YAML.load(request.body) when :json - body = request.raw_post - if body.blank? + if request.body.size == 0 {} else - data = ActiveSupport::JSON.decode(body) + data = ActiveSupport::JSON.decode(request.body) data = {:_json => data} unless data.is_a?(Hash) data.with_indifferent_access end diff --git a/actionpack/lib/action_dispatch/middleware/reloader.rb b/actionpack/lib/action_dispatch/middleware/reloader.rb deleted file mode 100644 index 67313e30e4..0000000000 --- a/actionpack/lib/action_dispatch/middleware/reloader.rb +++ /dev/null @@ -1,14 +0,0 @@ -module ActionDispatch - class Reloader - def initialize(app) - @app = app - end - - def call(env) - ActionController::Dispatcher.reload_application - @app.call(env) - ensure - ActionController::Dispatcher.cleanup_application - end - end -end diff --git a/actionpack/lib/action_dispatch/middleware/rescue.rb b/actionpack/lib/action_dispatch/middleware/rescue.rb new file mode 100644 index 0000000000..1456825526 --- /dev/null +++ b/actionpack/lib/action_dispatch/middleware/rescue.rb @@ -0,0 +1,14 @@ +module ActionDispatch + class Rescue + def initialize(app, rescuer) + @app, @rescuer = app, rescuer + end + + def call(env) + @app.call(env) + rescue Exception => exception + env['action_dispatch.rescue.exception'] = exception + @rescuer.call(env) + end + end +end diff --git a/actionpack/lib/action_dispatch/middleware/rewindable_input.rb b/actionpack/lib/action_dispatch/middleware/rewindable_input.rb deleted file mode 100644 index c818f28cce..0000000000 --- a/actionpack/lib/action_dispatch/middleware/rewindable_input.rb +++ /dev/null @@ -1,19 +0,0 @@ -module ActionDispatch - class RewindableInput - def initialize(app) - @app = app - end - - def call(env) - begin - env['rack.input'].rewind - rescue NoMethodError, Errno::ESPIPE - # Handles exceptions raised by input streams that cannot be rewound - # such as when using plain CGI under Apache - env['rack.input'] = StringIO.new(env['rack.input'].read) - end - - @app.call(env) - end - end -end diff --git a/actionpack/lib/action_dispatch/middleware/session/abstract_store.rb b/actionpack/lib/action_dispatch/middleware/session/abstract_store.rb index 6c039cf62d..6d109f4624 100644 --- a/actionpack/lib/action_dispatch/middleware/session/abstract_store.rb +++ b/actionpack/lib/action_dispatch/middleware/session/abstract_store.rb @@ -15,6 +15,7 @@ module ActionDispatch @by = by @env = env @loaded = false + @updated = false end def session_id @@ -26,12 +27,13 @@ module ActionDispatch def [](key) load! unless @loaded - super + super(key.to_s) end def []=(key, value) load! unless @loaded - super + super(key.to_s, value) + @updated = true end def to_hash @@ -40,6 +42,24 @@ module ActionDispatch h end + def update(hash = nil) + if hash.nil? + ActiveSupport::Deprecation.warn('use replace instead', caller) + replace({}) + else + super(hash.stringify_keys) + end + end + + def delete(key = nil) + if key.nil? + ActiveSupport::Deprecation.warn('use clear instead', caller) + clear + else + super(key.to_s) + end + end + def data ActiveSupport::Deprecation.warn( "ActionController::Session::AbstractStore::SessionHash#data " + @@ -47,6 +67,10 @@ module ActionDispatch to_hash end + def close + ActiveSupport::Deprecation.warn('sessions should no longer be closed', caller) + end + def inspect load! unless @loaded super @@ -57,11 +81,15 @@ module ActionDispatch @loaded end + def updated? + @updated + end + def load! stale_session_check! do id, session = @by.send(:load_session, @env) (@env[ENV_SESSION_OPTIONS_KEY] ||= {})[:id] = id - replace(session) + replace(session.stringify_keys) @loaded = true end end @@ -74,7 +102,7 @@ module ActionDispatch # Note that the regexp does not allow $1 to end with a ':' $1.constantize rescue LoadError, NameError => const_error - raise ActionController::SessionRestoreError, "Session contains objects whose class definition isn\\'t available.\nRemember to require the classes for all objects kept in the session.\n(Original exception: \#{const_error.message} [\#{const_error.class}])\n" + raise ActionController::SessionRestoreError, "Session contains objects whose class definition isn't available.\nRemember to require the classes for all objects kept in the session.\n(Original exception: #{const_error.message} [#{const_error.class}])\n" end retry @@ -125,7 +153,10 @@ module ActionDispatch options = env[ENV_SESSION_OPTIONS_KEY] if !session_data.is_a?(AbstractStore::SessionHash) || session_data.send(:loaded?) || options[:expire_after] - session_data.send(:load!) if session_data.is_a?(AbstractStore::SessionHash) && !session_data.send(:loaded?) + if session_data.is_a?(AbstractStore::SessionHash) + session_data.send(:load!) if !session_data.send(:loaded?) + return response if !session_data.send(:updated?) + end sid = options[:id] || generate_sid diff --git a/actionpack/lib/action_dispatch/middleware/session/cookie_store.rb b/actionpack/lib/action_dispatch/middleware/session/cookie_store.rb index 433c4cc070..547a2d2062 100644 --- a/actionpack/lib/action_dispatch/middleware/session/cookie_store.rb +++ b/actionpack/lib/action_dispatch/middleware/session/cookie_store.rb @@ -143,7 +143,8 @@ module ActionDispatch request = Rack::Request.new(env) session_data = request.cookies[@key] data = unmarshal(session_data) || persistent_session_id!({}) - [data[:session_id], data] + data.stringify_keys! + [data["session_id"], data] end # Marshal a session hash into safe cookie data. Include an integrity hash. @@ -206,12 +207,12 @@ module ActionDispatch end def inject_persistent_session_id(data) - requires_session_id?(data) ? { :session_id => generate_sid } : {} + requires_session_id?(data) ? { "session_id" => generate_sid } : {} end def requires_session_id?(data) if data - data.respond_to?(:key?) && !data.key?(:session_id) + data.respond_to?(:key?) && !data.key?("session_id") else true end diff --git a/actionpack/lib/action_dispatch/middleware/show_exceptions.rb b/actionpack/lib/action_dispatch/middleware/show_exceptions.rb new file mode 100644 index 0000000000..bfff307669 --- /dev/null +++ b/actionpack/lib/action_dispatch/middleware/show_exceptions.rb @@ -0,0 +1,141 @@ +module ActionDispatch + class ShowExceptions + include StatusCodes + + LOCALHOST = '127.0.0.1'.freeze + + RESCUES_TEMPLATE_PATH = File.join(File.dirname(__FILE__), 'templates') + + cattr_accessor :rescue_responses + @@rescue_responses = Hash.new(:internal_server_error) + @@rescue_responses.update({ + 'ActionController::RoutingError' => :not_found, + # TODO: Clean this up after the switch + ActionController::UnknownAction.name => :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 + }) + + cattr_accessor :rescue_templates + @@rescue_templates = Hash.new('diagnostics') + @@rescue_templates.update({ + 'ActionView::MissingTemplate' => 'missing_template', + 'ActionController::RoutingError' => 'routing_error', + ActionController::UnknownAction.name => 'unknown_action', + 'ActionView::TemplateError' => 'template_error' + }) + + FAILSAFE_RESPONSE = [500, {'Content-Type' => 'text/html'}, + ["<html><body><h1>500 Internal Server Error</h1>" << + "If you are the administrator of this website, then please read this web " << + "application's log file and/or the web server's log file to find out what " << + "went wrong.</body></html>"]] + + def initialize(app, consider_all_requests_local = false) + @app = app + @consider_all_requests_local = consider_all_requests_local + end + + def call(env) + @app.call(env) + rescue Exception => exception + raise exception if env['action_dispatch.show_exceptions'] == false + render_exception(env, exception) + end + + private + def render_exception(env, exception) + log_error(exception) + + request = Request.new(env) + if @consider_all_requests_local || local_request?(request) + rescue_action_locally(request, exception) + else + rescue_action_in_public(exception) + end + rescue Exception => failsafe_error + $stderr.puts "Error during failsafe response: #{failsafe_error}" + FAILSAFE_RESPONSE + end + + # Render detailed diagnostics for unhandled exceptions rescued from + # a controller action. + def rescue_action_locally(request, exception) + template = ActionView::Base.new([RESCUES_TEMPLATE_PATH], + :request => request, + :exception => exception + ) + file = "rescues/#{@@rescue_templates[exception.class.name]}.erb" + body = template.render(:file => file, :layout => 'rescues/layout.erb') + render(status_code(exception), body) + 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. At first, it will try to render a localized static page. + # For example, if a 500 error is being handled Rails and locale is :da, + # it will first attempt to render the file at <tt>public/500.da.html</tt> + # then attempt to render <tt>public/500.html</tt>. If none of them exist, + # the body of the response will be left empty. + def rescue_action_in_public(exception) + status = status_code(exception) + locale_path = "#{public_path}/#{status}.#{I18n.locale}.html" if I18n.locale + path = "#{public_path}/#{status}.html" + + if locale_path && File.exist?(locale_path) + render(status, File.read(locale_path)) + elsif File.exist?(path) + render(status, File.read(path)) + else + render(status, '') + end + end + + # True if the request came from localhost, 127.0.0.1. + def local_request?(request) + request.remote_addr == LOCALHOST && request.remote_ip == LOCALHOST + end + + def status_code(exception) + interpret_status(@@rescue_responses[exception.class.name]).to_i + end + + def render(status, body) + [status, {'Content-Type' => 'text/html', 'Content-Length' => body.length.to_s}, [body]] + end + + def public_path + defined?(Rails.public_path) ? Rails.public_path : 'public_path' + end + + def log_error(exception) + return unless logger + + ActiveSupport::Deprecation.silence do + if ActionView::TemplateError === exception + logger.fatal(exception.to_s) + else + logger.fatal( + "\n#{exception.class} (#{exception.message}):\n " + + clean_backtrace(exception).join("\n ") + "\n\n" + ) + end + end + end + + def clean_backtrace(exception) + defined?(Rails) && Rails.respond_to?(:backtrace_cleaner) ? + Rails.backtrace_cleaner.clean(exception.backtrace) : + exception.backtrace + end + + def logger + defined?(Rails.logger) ? Rails.logger : Logger.new($stderr) + end + end +end diff --git a/actionpack/lib/action_dispatch/middleware/stack.rb b/actionpack/lib/action_dispatch/middleware/stack.rb index ee5f28d5cb..ade2d6f05e 100644 --- a/actionpack/lib/action_dispatch/middleware/stack.rb +++ b/actionpack/lib/action_dispatch/middleware/stack.rb @@ -34,8 +34,6 @@ module ActionDispatch else @klass.to_s.constantize end - rescue NameError - @klass end def active? @@ -61,7 +59,7 @@ module ActionDispatch def inspect str = klass.to_s - args.each { |arg| str += ", #{arg.inspect}" } + args.each { |arg| str += ", #{build_args.inspect}" } str end @@ -74,7 +72,6 @@ module ActionDispatch end private - def build_args Array(args).map { |arg| arg.respond_to?(:call) ? arg.call : arg } end diff --git a/actionpack/lib/action_dispatch/middleware/templates/rescues/_request_and_response.erb b/actionpack/lib/action_dispatch/middleware/templates/rescues/_request_and_response.erb new file mode 100644 index 0000000000..5224403dab --- /dev/null +++ b/actionpack/lib/action_dispatch/middleware/templates/rescues/_request_and_response.erb @@ -0,0 +1,24 @@ +<% unless @exception.blamed_files.blank? %> + <% if (hide = @exception.blamed_files.length > 8) %> + <a href="#" onclick="document.getElementById('blame_trace').style.display='block'; return false;">Show blamed files</a> + <% end %> + <pre id="blame_trace" <%='style="display:none"' if hide %>><code><%=h @exception.describe_blame %></code></pre> +<% end %> + +<% + clean_params = @request.parameters.clone + clean_params.delete("action") + clean_params.delete("controller") + + request_dump = clean_params.empty? ? 'None' : clean_params.inspect.gsub(',', ",\n") +%> + +<h2 style="margin-top: 30px">Request</h2> +<p><b>Parameters</b>: <pre><%=h request_dump %></pre></p> + +<p><a href="#" onclick="document.getElementById('session_dump').style.display='block'; return false;">Show session dump</a></p> +<div id="session_dump" style="display:none"><%= debug(@request.session.instance_variable_get("@data")) %></div> + + +<h2 style="margin-top: 30px">Response</h2> +<p><b>Headers</b>: <pre><%=h @response ? @response.headers.inspect.gsub(',', ",\n") : 'None' %></pre></p> diff --git a/actionpack/lib/action_dispatch/middleware/templates/rescues/_trace.erb b/actionpack/lib/action_dispatch/middleware/templates/rescues/_trace.erb new file mode 100644 index 0000000000..bb2d8375bd --- /dev/null +++ b/actionpack/lib/action_dispatch/middleware/templates/rescues/_trace.erb @@ -0,0 +1,26 @@ +<% + traces = [ + ["Application Trace", @exception.application_backtrace], + ["Framework Trace", @exception.framework_backtrace], + ["Full Trace", @exception.clean_backtrace] + ] + names = traces.collect {|name, trace| name} +%> + +<p><code>RAILS_ROOT: <%= defined?(RAILS_ROOT) ? RAILS_ROOT : "unset" %></code></p> + +<div id="traces"> + <% names.each do |name| %> + <% + show = "document.getElementById('#{name.gsub /\s/, '-'}').style.display='block';" + hide = (names - [name]).collect {|hide_name| "document.getElementById('#{hide_name.gsub /\s/, '-'}').style.display='none';"} + %> + <a href="#" onclick="<%= hide %><%= show %>; return false;"><%= name %></a> <%= '|' unless names.last == name %> + <% end %> + + <% traces.each do |name, trace| %> + <div id="<%= name.gsub /\s/, '-' %>" style="display: <%= name == "Application Trace" ? 'block' : 'none' %>;"> + <pre><code><%= trace.join "\n" %></code></pre> + </div> + <% end %> +</div> diff --git a/actionpack/lib/action_dispatch/middleware/templates/rescues/diagnostics.erb b/actionpack/lib/action_dispatch/middleware/templates/rescues/diagnostics.erb new file mode 100644 index 0000000000..693e56270a --- /dev/null +++ b/actionpack/lib/action_dispatch/middleware/templates/rescues/diagnostics.erb @@ -0,0 +1,10 @@ +<h1> + <%=h @exception.class.to_s %> + <% if @request.parameters['controller'] %> + in <%=h @request.parameters['controller'].humanize %>Controller<% if @request.parameters['action'] %>#<%=h @request.parameters['action'] %><% end %> + <% end %> +</h1> +<pre><%=h @exception.clean_message %></pre> + +<%= render :file => "rescues/_trace.erb" %> +<%= render :file => "rescues/_request_and_response.erb" %> diff --git a/actionpack/lib/action_dispatch/middleware/templates/rescues/layout.erb b/actionpack/lib/action_dispatch/middleware/templates/rescues/layout.erb new file mode 100644 index 0000000000..6c32fb17b8 --- /dev/null +++ b/actionpack/lib/action_dispatch/middleware/templates/rescues/layout.erb @@ -0,0 +1,29 @@ +<html xmlns="http://www.w3.org/1999/xhtml"> +<head> + <title>Action Controller: Exception caught</title> + <style> + body { background-color: #fff; color: #333; } + + body, p, ol, ul, td { + font-family: verdana, arial, helvetica, sans-serif; + font-size: 13px; + line-height: 18px; + } + + pre { + background-color: #eee; + padding: 10px; + font-size: 11px; + } + + a { color: #000; } + a:visited { color: #666; } + a:hover { color: #fff; background-color:#000; } + </style> +</head> +<body> + +<%= yield %> + +</body> +</html> diff --git a/actionpack/lib/action_dispatch/middleware/templates/rescues/missing_template.erb b/actionpack/lib/action_dispatch/middleware/templates/rescues/missing_template.erb new file mode 100644 index 0000000000..dbfdf76947 --- /dev/null +++ b/actionpack/lib/action_dispatch/middleware/templates/rescues/missing_template.erb @@ -0,0 +1,2 @@ +<h1>Template is missing</h1> +<p><%=h @exception.message %></p> diff --git a/actionpack/lib/action_dispatch/middleware/templates/rescues/routing_error.erb b/actionpack/lib/action_dispatch/middleware/templates/rescues/routing_error.erb new file mode 100644 index 0000000000..ccfa858cce --- /dev/null +++ b/actionpack/lib/action_dispatch/middleware/templates/rescues/routing_error.erb @@ -0,0 +1,10 @@ +<h1>Routing Error</h1> +<p><pre><%=h @exception.message %></pre></p> +<% unless @exception.failures.empty? %><p> + <h2>Failure reasons:</h2> + <ol> + <% @exception.failures.each do |route, reason| %> + <li><code><%=h route.inspect.gsub('\\', '') %></code> failed because <%=h reason.downcase %></li> + <% end %> + </ol> +</p><% end %> diff --git a/actionpack/lib/action_dispatch/middleware/templates/rescues/template_error.erb b/actionpack/lib/action_dispatch/middleware/templates/rescues/template_error.erb new file mode 100644 index 0000000000..02fa18211d --- /dev/null +++ b/actionpack/lib/action_dispatch/middleware/templates/rescues/template_error.erb @@ -0,0 +1,21 @@ +<h1> + <%=h @exception.original_exception.class.to_s %> in + <%=h @request.parameters["controller"].capitalize if @request.parameters["controller"]%>#<%=h @request.parameters["action"] %> +</h1> + +<p> + Showing <i><%=h @exception.file_name %></i> where line <b>#<%=h @exception.line_number %></b> raised: + <pre><code><%=h @exception.message %></code></pre> +</p> + +<p>Extracted source (around line <b>#<%=h @exception.line_number %></b>): +<pre><code><%=h @exception.source_extract %></code></pre></p> + +<p><%=h @exception.sub_template_message %></p> + +<% @real_exception = @exception + @exception = @exception.original_exception || @exception %> +<%= render :file => "rescues/_trace.erb" %> +<% @exception = @real_exception %> + +<%= render :file => "rescues/_request_and_response.erb" %> diff --git a/actionpack/lib/action_dispatch/middleware/templates/rescues/unknown_action.erb b/actionpack/lib/action_dispatch/middleware/templates/rescues/unknown_action.erb new file mode 100644 index 0000000000..683379da10 --- /dev/null +++ b/actionpack/lib/action_dispatch/middleware/templates/rescues/unknown_action.erb @@ -0,0 +1,2 @@ +<h1>Unknown action</h1> +<p><%=h @exception.message %></p> |