aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorJosé Valim <jose.valim@gmail.com>2011-12-01 21:18:47 +0100
committerJosé Valim <jose.valim@gmail.com>2011-12-01 21:18:47 +0100
commit07f90f6bec702a6e08d65a33682ddd244050375f (patch)
treeeb76e17734ae44e08a75b8d74740e6e600422ae6
parent83d29a283ceb13ff431da535e8cc35db6828016c (diff)
parent6a4606d3a64e60189ea4ba5243830dcd97e6de14 (diff)
downloadrails-07f90f6bec702a6e08d65a33682ddd244050375f.tar.gz
rails-07f90f6bec702a6e08d65a33682ddd244050375f.tar.bz2
rails-07f90f6bec702a6e08d65a33682ddd244050375f.zip
Merge branch 'exceptions' with the following features:
* A Railtie API for registering new exceptions and their respective status code (check Active Record railtie for an example) * Extraction of ShowExceptions middleware logging and debugging features into a middleware called DebugExceptions Conflicts: actionpack/CHANGELOG.md
-rw-r--r--actionpack/CHANGELOG.md2
-rw-r--r--actionpack/lib/action_controller/log_subscriber.rb2
-rw-r--r--actionpack/lib/action_dispatch.rb2
-rw-r--r--actionpack/lib/action_dispatch/middleware/debug_exceptions.rb77
-rw-r--r--actionpack/lib/action_dispatch/middleware/exception_wrapper.rb78
-rw-r--r--actionpack/lib/action_dispatch/middleware/show_exceptions.rb181
-rw-r--r--actionpack/lib/action_dispatch/railtie.rb13
-rw-r--r--actionpack/test/abstract_unit.rb22
-rw-r--r--actionpack/test/controller/show_exceptions_test.rb1
-rw-r--r--actionpack/test/dispatch/debug_exceptions_test.rb116
-rw-r--r--actionpack/test/dispatch/show_exceptions_test.rb95
-rw-r--r--activerecord/lib/active_record/railtie.rb7
-rw-r--r--railties/CHANGELOG.md6
-rw-r--r--railties/lib/rails/application.rb3
-rw-r--r--railties/test/application/middleware/exceptions_test.rb (renamed from railties/test/application/middleware/show_exceptions_test.rb)31
-rw-r--r--railties/test/application/middleware_test.rb24
16 files changed, 406 insertions, 254 deletions
diff --git a/actionpack/CHANGELOG.md b/actionpack/CHANGELOG.md
index 6037e251f2..0545d36a0a 100644
--- a/actionpack/CHANGELOG.md
+++ b/actionpack/CHANGELOG.md
@@ -1,5 +1,7 @@
## Rails 3.2.0 (unreleased) ##
+* Allow rescue responses to be configured through a railtie as in `config.action_dispatch.rescue_responses`. Please look at ActiveRecord::Railtie for an example *José Valim*
+
* Allow fresh_when/stale? to take a record instead of an options hash *DHH*
* Assets should use the request protocol by default or default to relative if no request is available *Jonathan del Strother*
diff --git a/actionpack/lib/action_controller/log_subscriber.rb b/actionpack/lib/action_controller/log_subscriber.rb
index 8666ecc9c8..de250053d3 100644
--- a/actionpack/lib/action_controller/log_subscriber.rb
+++ b/actionpack/lib/action_controller/log_subscriber.rb
@@ -20,7 +20,7 @@ module ActionController
status = payload[:status]
if status.nil? && payload[:exception].present?
- status = Rack::Utils.status_code(ActionDispatch::ShowExceptions.rescue_responses[payload[:exception].first]) rescue nil
+ status = Rack::Utils.status_code(ActionDispatch::ExceptionWrapper.new({}, payload[:exception]).status_code)
end
message = "Completed #{status} #{Rack::Utils::HTTP_STATUS_CODES[status]} in %.0fms" % event.duration
message << " (#{additions.join(" | ")})" unless additions.blank?
diff --git a/actionpack/lib/action_dispatch.rb b/actionpack/lib/action_dispatch.rb
index 1e92d14542..89f515ee3d 100644
--- a/actionpack/lib/action_dispatch.rb
+++ b/actionpack/lib/action_dispatch.rb
@@ -51,6 +51,8 @@ module ActionDispatch
autoload :BestStandardsSupport
autoload :Callbacks
autoload :Cookies
+ autoload :DebugExceptions
+ autoload :ExceptionWrapper
autoload :Flash
autoload :Head
autoload :ParamsParser
diff --git a/actionpack/lib/action_dispatch/middleware/debug_exceptions.rb b/actionpack/lib/action_dispatch/middleware/debug_exceptions.rb
new file mode 100644
index 0000000000..5da21ddd3d
--- /dev/null
+++ b/actionpack/lib/action_dispatch/middleware/debug_exceptions.rb
@@ -0,0 +1,77 @@
+require 'action_dispatch/http/request'
+require 'action_dispatch/middleware/exception_wrapper'
+
+module ActionDispatch
+ # This middleware is responsible for logging exceptions and
+ # showing a debugging page in case the request is local.
+ class DebugExceptions
+ RESCUES_TEMPLATE_PATH = File.join(File.dirname(__FILE__), 'templates')
+
+ def initialize(app)
+ @app = app
+ end
+
+ def call(env)
+ begin
+ response = @app.call(env)
+
+ if response[1]['X-Cascade'] == 'pass'
+ raise ActionController::RoutingError, "No route matches [#{env['REQUEST_METHOD']}] #{env['PATH_INFO'].inspect}"
+ end
+ rescue Exception => exception
+ raise exception if env['action_dispatch.show_exceptions'] == false
+ end
+
+ response ? response : render_exception(env, exception)
+ end
+
+ private
+
+ def render_exception(env, exception)
+ wrapper = ExceptionWrapper.new(env, exception)
+ log_error(env, wrapper)
+
+ if env['action_dispatch.show_detailed_exceptions']
+ template = ActionView::Base.new([RESCUES_TEMPLATE_PATH],
+ :request => Request.new(env),
+ :exception => wrapper.exception,
+ :application_trace => wrapper.application_trace,
+ :framework_trace => wrapper.framework_trace,
+ :full_trace => wrapper.full_trace
+ )
+
+ file = "rescues/#{wrapper.rescue_template}"
+ body = template.render(:template => file, :layout => 'rescues/layout')
+ render(wrapper.status_code, body)
+ else
+ raise exception
+ end
+ end
+
+ def render(status, body)
+ [status, {'Content-Type' => "text/html; charset=#{Response.default_charset}", 'Content-Length' => body.bytesize.to_s}, [body]]
+ end
+
+ def log_error(env, wrapper)
+ logger = logger(env)
+ return unless logger
+
+ exception = wrapper.exception
+
+ ActiveSupport::Deprecation.silence do
+ message = "\n#{exception.class} (#{exception.message}):\n"
+ message << exception.annoted_source_code.to_s if exception.respond_to?(:annoted_source_code)
+ message << " " << wrapper.application_trace.join("\n ")
+ logger.fatal("#{message}\n\n")
+ end
+ end
+
+ def logger(env)
+ env['action_dispatch.logger'] || stderr_logger
+ end
+
+ def stderr_logger
+ @stderr_logger ||= Logger.new($stderr)
+ end
+ end
+end
diff --git a/actionpack/lib/action_dispatch/middleware/exception_wrapper.rb b/actionpack/lib/action_dispatch/middleware/exception_wrapper.rb
new file mode 100644
index 0000000000..c0532c80c4
--- /dev/null
+++ b/actionpack/lib/action_dispatch/middleware/exception_wrapper.rb
@@ -0,0 +1,78 @@
+require 'action_controller/metal/exceptions'
+require 'active_support/core_ext/exception'
+
+module ActionDispatch
+ class ExceptionWrapper
+ cattr_accessor :rescue_responses
+ @@rescue_responses = Hash.new(:internal_server_error)
+ @@rescue_responses.merge!(
+ 'ActionController::RoutingError' => :not_found,
+ 'AbstractController::ActionNotFound' => :not_found,
+ 'ActionController::MethodNotAllowed' => :method_not_allowed,
+ 'ActionController::NotImplemented' => :not_implemented,
+ 'ActionController::InvalidAuthenticityToken' => :unprocessable_entity
+ )
+
+ cattr_accessor :rescue_templates
+ @@rescue_templates = Hash.new('diagnostics')
+ @@rescue_templates.merge!(
+ 'ActionView::MissingTemplate' => 'missing_template',
+ 'ActionController::RoutingError' => 'routing_error',
+ 'AbstractController::ActionNotFound' => 'unknown_action',
+ 'ActionView::Template::Error' => 'template_error'
+ )
+
+ attr_reader :env, :exception
+
+ def initialize(env, exception)
+ @env = env
+ @exception = original_exception(exception)
+ end
+
+ def rescue_template
+ @@rescue_templates[@exception.class.name]
+ end
+
+ def status_code
+ Rack::Utils.status_code(@@rescue_responses[@exception.class.name])
+ end
+
+ def application_trace
+ clean_backtrace(:silent)
+ end
+
+ def framework_trace
+ clean_backtrace(:noise)
+ end
+
+ def full_trace
+ clean_backtrace(:all)
+ end
+
+ private
+
+ def original_exception(exception)
+ if registered_original_exception?(exception)
+ exception.original_exception
+ else
+ exception
+ end
+ end
+
+ def registered_original_exception?(exception)
+ exception.respond_to?(:original_exception) && @@rescue_responses.has_key?(exception.original_exception.class.name)
+ end
+
+ def clean_backtrace(*args)
+ if backtrace_cleaner
+ backtrace_cleaner.clean(@exception.backtrace, *args)
+ else
+ @exception.backtrace
+ end
+ end
+
+ def backtrace_cleaner
+ @backtrace_cleaner ||= @env['action_dispatch.backtrace_cleaner']
+ end
+ end
+end \ No newline at end of file
diff --git a/actionpack/lib/action_dispatch/middleware/show_exceptions.rb b/actionpack/lib/action_dispatch/middleware/show_exceptions.rb
index c850e25507..c801bcf2ef 100644
--- a/actionpack/lib/action_dispatch/middleware/show_exceptions.rb
+++ b/actionpack/lib/action_dispatch/middleware/show_exceptions.rb
@@ -1,44 +1,33 @@
-require 'active_support/core_ext/exception'
-require 'action_controller/metal/exceptions'
-require 'active_support/notifications'
require 'action_dispatch/http/request'
+require 'action_dispatch/middleware/exception_wrapper'
require 'active_support/deprecation'
module ActionDispatch
- # This middleware rescues any exception returned by the application and renders
- # nice exception pages if it's being rescued locally.
+ # This middleware rescues any exception returned by the application
+ # and wraps them in a format for the end user.
class ShowExceptions
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,
- 'AbstractController::ActionNotFound' => :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',
- 'AbstractController::ActionNotFound' => 'unknown_action',
- 'ActionView::Template::Error' => '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>"]]
+ class << self
+ def rescue_responses
+ ActiveSupport::Deprecation.warn "ActionDispatch::ShowExceptions.rescue_responses is deprecated. " \
+ "Please configure your exceptions using a railtie or in your application config instead."
+ ExceptionWrapper.rescue_responses
+ end
+
+ def rescue_templates
+ ActiveSupport::Deprecation.warn "ActionDispatch::ShowExceptions.rescue_templates is deprecated. " \
+ "Please configure your exceptions using a railtie or in your application config instead."
+ ExceptionWrapper.rescue_templates
+ end
+ end
+
def initialize(app, consider_all_requests_local = nil)
ActiveSupport::Deprecation.warn "Passing consider_all_requests_local option to ActionDispatch::ShowExceptions middleware no longer works" unless consider_all_requests_local.nil?
@app = app
@@ -46,131 +35,47 @@ module ActionDispatch
def call(env)
begin
- status, headers, body = @app.call(env)
- exception = nil
-
- # Only this middleware cares about RoutingError. So, let's just raise
- # it here.
- if headers['X-Cascade'] == 'pass'
- raise ActionController::RoutingError, "No route matches [#{env['REQUEST_METHOD']}] #{env['PATH_INFO'].inspect}"
- end
+ response = @app.call(env)
rescue Exception => exception
raise exception if env['action_dispatch.show_exceptions'] == false
end
- exception ? render_exception(env, exception) : [status, headers, body]
+ response ? response : render_exception_with_failsafe(env, exception)
end
private
- def render_exception(env, exception)
- log_error(env, exception)
- exception = original_exception(exception)
-
- if env['action_dispatch.show_detailed_exceptions'] == true
- rescue_action_diagnostics(env, exception)
- else
- rescue_action_error_page(exception)
- end
- rescue Exception => failsafe_error
- $stderr.puts "Error during failsafe response: #{failsafe_error}\n #{failsafe_error.backtrace * "\n "}"
- FAILSAFE_RESPONSE
- end
-
- # Render detailed diagnostics for unhandled exceptions rescued from
- # a controller action.
- def rescue_action_diagnostics(env, exception)
- template = ActionView::Base.new([RESCUES_TEMPLATE_PATH],
- :request => Request.new(env),
- :exception => exception,
- :application_trace => application_trace(env, exception),
- :framework_trace => framework_trace(env, exception),
- :full_trace => full_trace(env, exception)
- )
- file = "rescues/#{@@rescue_templates[exception.class.name]}"
- body = template.render(:template => file, :layout => 'rescues/layout')
- 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_error_page(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
-
- def status_code(exception)
- Rack::Utils.status_code(@@rescue_responses[exception.class.name])
- end
-
- def render(status, body)
- [status, {'Content-Type' => "text/html; charset=#{Response.default_charset}", 'Content-Length' => body.bytesize.to_s}, [body]]
- end
-
- def public_path
- defined?(Rails.public_path) ? Rails.public_path : 'public_path'
- end
-
- def log_error(env, exception)
- return unless logger(env)
-
- ActiveSupport::Deprecation.silence do
- message = "\n#{exception.class} (#{exception.message}):\n"
- message << exception.annoted_source_code.to_s if exception.respond_to?(:annoted_source_code)
- message << " " << application_trace(env, exception).join("\n ")
- logger(env).fatal("#{message}\n\n")
- end
- end
-
- def application_trace(env, exception)
- clean_backtrace(env, exception, :silent)
- end
-
- def framework_trace(env, exception)
- clean_backtrace(env, exception, :noise)
- end
- def full_trace(env, exception)
- clean_backtrace(env, exception, :all)
- end
+ def render_exception_with_failsafe(env, exception)
+ render_exception(env, exception)
+ rescue Exception => failsafe_error
+ $stderr.puts "Error during failsafe response: #{failsafe_error}\n #{failsafe_error.backtrace * "\n "}"
+ FAILSAFE_RESPONSE
+ end
- def clean_backtrace(env, exception, *args)
- env['action_dispatch.backtrace_cleaner'] ?
- env['action_dispatch.backtrace_cleaner'].clean(exception.backtrace, *args) :
- exception.backtrace
- end
+ def render_exception(env, exception)
+ wrapper = ExceptionWrapper.new(env, exception)
- def logger(env)
- env['action_dispatch.logger'] || stderr_logger
- end
+ status = wrapper.status_code
+ locale_path = "#{public_path}/#{status}.#{I18n.locale}.html" if I18n.locale
+ path = "#{public_path}/#{status}.html"
- def stderr_logger
- Logger.new($stderr)
- end
-
- def original_exception(exception)
- if registered_original_exception?(exception)
- exception.original_exception
+ if locale_path && File.exist?(locale_path)
+ render(status, File.read(locale_path))
+ elsif File.exist?(path)
+ render(status, File.read(path))
else
- exception
+ render(status, '')
end
end
- def registered_original_exception?(exception)
- exception.respond_to?(:original_exception) && @@rescue_responses.has_key?(exception.original_exception.class.name)
+ def render(status, body)
+ [status, {'Content-Type' => "text/html; charset=#{Response.default_charset}", 'Content-Length' => body.bytesize.to_s}, [body]]
+ end
+
+ # TODO: Make this a middleware initialization parameter once
+ # we removed the second option (which is deprecated)
+ def public_path
+ defined?(Rails.public_path) ? Rails.public_path : 'public_path'
end
end
end
diff --git a/actionpack/lib/action_dispatch/railtie.rb b/actionpack/lib/action_dispatch/railtie.rb
index f18ebabf29..2ba3198ebd 100644
--- a/actionpack/lib/action_dispatch/railtie.rb
+++ b/actionpack/lib/action_dispatch/railtie.rb
@@ -9,11 +9,22 @@ module ActionDispatch
config.action_dispatch.best_standards_support = true
config.action_dispatch.tld_length = 1
config.action_dispatch.ignore_accept_header = false
- config.action_dispatch.rack_cache = {:metastore => "rails:/", :entitystore => "rails:/", :verbose => true}
+ config.action_dispatch.rescue_templates = { }
+ config.action_dispatch.rescue_responses = { }
+
+ config.action_dispatch.rack_cache = {
+ :metastore => "rails:/",
+ :entitystore => "rails:/",
+ :verbose => true
+ }
+
initializer "action_dispatch.configure" do |app|
ActionDispatch::Http::URL.tld_length = app.config.action_dispatch.tld_length
ActionDispatch::Request.ignore_accept_header = app.config.action_dispatch.ignore_accept_header
+ ActionDispatch::ExceptionWrapper.rescue_responses.merge!(config.action_dispatch.rescue_responses)
+ ActionDispatch::ExceptionWrapper.rescue_templates.merge!(config.action_dispatch.rescue_templates)
+
config.action_dispatch.always_write_cookie = Rails.env.development? if config.action_dispatch.always_write_cookie.nil?
ActionDispatch::Cookies::CookieJar.always_write_cookie = config.action_dispatch.always_write_cookie
end
diff --git a/actionpack/test/abstract_unit.rb b/actionpack/test/abstract_unit.rb
index d191a203dd..c95b8221a1 100644
--- a/actionpack/test/abstract_unit.rb
+++ b/actionpack/test/abstract_unit.rb
@@ -175,6 +175,7 @@ class ActionDispatch::IntegrationTest < ActiveSupport::TestCase
def self.build_app(routes = nil)
RoutedRackApp.new(routes || ActionDispatch::Routing::RouteSet.new) do |middleware|
middleware.use "ActionDispatch::ShowExceptions"
+ middleware.use "ActionDispatch::DebugExceptions"
middleware.use "ActionDispatch::Callbacks"
middleware.use "ActionDispatch::ParamsParser"
middleware.use "ActionDispatch::Cookies"
@@ -338,16 +339,19 @@ end
module ActionDispatch
class ShowExceptions
private
- remove_method :public_path
- def public_path
- "#{FIXTURE_LOAD_PATH}/public"
- end
+ remove_method :public_path
+ def public_path
+ "#{FIXTURE_LOAD_PATH}/public"
+ end
+ end
- remove_method :stderr_logger
- # Silence logger
- def stderr_logger
- nil
- end
+ class DebugExceptions
+ private
+ remove_method :stderr_logger
+ # Silence logger
+ def stderr_logger
+ nil
+ end
end
end
diff --git a/actionpack/test/controller/show_exceptions_test.rb b/actionpack/test/controller/show_exceptions_test.rb
index 74067cb895..5eff1eb09d 100644
--- a/actionpack/test/controller/show_exceptions_test.rb
+++ b/actionpack/test/controller/show_exceptions_test.rb
@@ -3,6 +3,7 @@ require 'abstract_unit'
module ShowExceptions
class ShowExceptionsController < ActionController::Base
use ActionDispatch::ShowExceptions
+ use ActionDispatch::DebugExceptions
def boom
raise 'boom!'
diff --git a/actionpack/test/dispatch/debug_exceptions_test.rb b/actionpack/test/dispatch/debug_exceptions_test.rb
new file mode 100644
index 0000000000..e6c0a06878
--- /dev/null
+++ b/actionpack/test/dispatch/debug_exceptions_test.rb
@@ -0,0 +1,116 @@
+require 'abstract_unit'
+
+class DebugExceptionsTest < ActionDispatch::IntegrationTest
+
+ class Boomer
+ def initialize(detailed = false)
+ @detailed = detailed
+ end
+
+ def call(env)
+ env['action_dispatch.show_detailed_exceptions'] = @detailed
+ req = ActionDispatch::Request.new(env)
+ case req.path
+ when "/not_found"
+ raise ActionController::UnknownAction
+ when "/runtime_error"
+ raise RuntimeError
+ when "/method_not_allowed"
+ raise ActionController::MethodNotAllowed
+ when "/not_implemented"
+ raise ActionController::NotImplemented
+ when "/unprocessable_entity"
+ raise ActionController::InvalidAuthenticityToken
+ when "/not_found_original_exception"
+ raise ActionView::Template::Error.new('template', {}, AbstractController::ActionNotFound.new)
+ else
+ raise "puke!"
+ end
+ end
+ end
+
+ ProductionApp = ActionDispatch::DebugExceptions.new((Boomer.new(false)))
+ DevelopmentApp = ActionDispatch::DebugExceptions.new((Boomer.new(true)))
+
+ test 'skip diagnosis if not showing detailed exceptions' do
+ @app = ProductionApp
+ assert_raise RuntimeError do
+ get "/", {}, {'action_dispatch.show_exceptions' => true}
+ end
+ end
+
+ test 'skip diagnosis if not showing exceptions' do
+ @app = DevelopmentApp
+ assert_raise RuntimeError do
+ get "/", {}, {'action_dispatch.show_exceptions' => false}
+ end
+ end
+
+ test "rescue with diagnostics message" do
+ @app = DevelopmentApp
+
+ get "/", {}, {'action_dispatch.show_exceptions' => true}
+ assert_response 500
+ assert_match(/puke/, body)
+
+ get "/not_found", {}, {'action_dispatch.show_exceptions' => true}
+ assert_response 404
+ assert_match(/#{ActionController::UnknownAction.name}/, body)
+
+ get "/method_not_allowed", {}, {'action_dispatch.show_exceptions' => true}
+ assert_response 405
+ assert_match(/ActionController::MethodNotAllowed/, body)
+ end
+
+ test "does not show filtered parameters" do
+ @app = DevelopmentApp
+
+ get "/", {"foo"=>"bar"}, {'action_dispatch.show_exceptions' => true,
+ 'action_dispatch.parameter_filter' => [:foo]}
+ assert_response 500
+ assert_match("&quot;foo&quot;=&gt;&quot;[FILTERED]&quot;", body)
+ end
+
+ test "show registered original exception for wrapped exceptions" do
+ @app = DevelopmentApp
+
+ get "/not_found_original_exception", {}, {'action_dispatch.show_exceptions' => true}
+ assert_response 404
+ assert_match(/AbstractController::ActionNotFound/, body)
+ end
+
+ test "show the controller name in the diagnostics template when controller name is present" do
+ @app = DevelopmentApp
+ get("/runtime_error", {}, {
+ 'action_dispatch.show_exceptions' => true,
+ 'action_dispatch.request.parameters' => {
+ 'action' => 'show',
+ 'id' => 'unknown',
+ 'controller' => 'featured_tile'
+ }
+ })
+ assert_response 500
+ assert_match(/RuntimeError\n in FeaturedTileController/, body)
+ end
+
+ test "sets the HTTP charset parameter" do
+ @app = DevelopmentApp
+
+ get "/", {}, {'action_dispatch.show_exceptions' => true}
+ assert_equal "text/html; charset=utf-8", response.headers["Content-Type"]
+ end
+
+ test 'uses logger from env' do
+ @app = DevelopmentApp
+ output = StringIO.new
+ get "/", {}, {'action_dispatch.show_exceptions' => true, 'action_dispatch.logger' => Logger.new(output)}
+ assert_match(/puke/, output.rewind && output.read)
+ end
+
+ test 'uses backtrace cleaner from env' do
+ @app = DevelopmentApp
+ cleaner = stub(:clean => ['passed backtrace cleaner'])
+ get "/", {}, {'action_dispatch.show_exceptions' => true, 'action_dispatch.backtrace_cleaner' => cleaner}
+ assert_match(/passed backtrace cleaner/, body)
+ end
+end
diff --git a/actionpack/test/dispatch/show_exceptions_test.rb b/actionpack/test/dispatch/show_exceptions_test.rb
index 90f13a3bb9..020cc80f3f 100644
--- a/actionpack/test/dispatch/show_exceptions_test.rb
+++ b/actionpack/test/dispatch/show_exceptions_test.rb
@@ -3,24 +3,13 @@ require 'abstract_unit'
class ShowExceptionsTest < ActionDispatch::IntegrationTest
class Boomer
- def initialize(detailed = false)
- @detailed = detailed
- end
-
def call(env)
- env['action_dispatch.show_detailed_exceptions'] = @detailed
req = ActionDispatch::Request.new(env)
case req.path
when "/not_found"
raise ActionController::UnknownAction
- when "/runtime_error"
- raise RuntimeError
when "/method_not_allowed"
raise ActionController::MethodNotAllowed
- when "/not_implemented"
- raise ActionController::NotImplemented
- when "/unprocessable_entity"
- raise ActionController::InvalidAuthenticityToken
when "/not_found_original_exception"
raise ActionView::Template::Error.new('template', {}, AbstractController::ActionNotFound.new)
else
@@ -29,10 +18,16 @@ class ShowExceptionsTest < ActionDispatch::IntegrationTest
end
end
- ProductionApp = ActionDispatch::ShowExceptions.new(Boomer.new(false))
- DevelopmentApp = ActionDispatch::ShowExceptions.new(Boomer.new(true))
+ ProductionApp = ActionDispatch::ShowExceptions.new(Boomer.new)
+
+ test 'skip diagnosis if not showing exceptions' do
+ @app = ProductionApp
+ assert_raise RuntimeError do
+ get "/", {}, {'action_dispatch.show_exceptions' => false}
+ end
+ end
- test "rescue with error page when show_exceptions is false" do
+ test "rescue with error page" do
@app = ProductionApp
get "/", {}, {'action_dispatch.show_exceptions' => true}
@@ -48,24 +43,7 @@ class ShowExceptionsTest < ActionDispatch::IntegrationTest
assert_equal "", body
end
- test "rescue with diagnostics message when show_exceptions is true" do
- @app = DevelopmentApp
-
- get "/", {}, {'action_dispatch.show_exceptions' => true}
- assert_response 500
- assert_match(/puke/, body)
-
- get "/not_found", {}, {'action_dispatch.show_exceptions' => true}
- assert_response 404
- assert_match(/#{ActionController::UnknownAction.name}/, body)
-
- get "/method_not_allowed", {}, {'action_dispatch.show_exceptions' => true}
- assert_response 405
- assert_match(/ActionController::MethodNotAllowed/, body)
- end
-
test "localize rescue error page" do
- # Change locale
old_locale, I18n.locale = I18n.locale, :da
begin
@@ -83,63 +61,18 @@ class ShowExceptionsTest < ActionDispatch::IntegrationTest
end
end
- test "does not show filtered parameters" do
- @app = DevelopmentApp
-
- get "/", {"foo"=>"bar"}, {'action_dispatch.show_exceptions' => true,
- 'action_dispatch.parameter_filter' => [:foo]}
- assert_response 500
- assert_match("&quot;foo&quot;=&gt;&quot;[FILTERED]&quot;", body)
- end
-
- test "show registered original exception for wrapped exceptions when show_exceptions is false" do
- @app = ProductionApp
-
- get "/not_found_original_exception", {}, {'action_dispatch.show_exceptions' => true}
- assert_response 404
- assert_match(/404 error/, body)
- end
-
- test "show registered original exception for wrapped exceptions when show_exceptions is true" do
- @app = DevelopmentApp
-
- get "/not_found_original_exception", {}, {'action_dispatch.show_exceptions' => true}
- assert_response 404
- assert_match(/AbstractController::ActionNotFound/, body)
- end
-
- test "show the controller name in the diagnostics template when controller name is present" do
- @app = DevelopmentApp
- get("/runtime_error", {}, {
- 'action_dispatch.show_exceptions' => true,
- 'action_dispatch.request.parameters' => {
- 'action' => 'show',
- 'id' => 'unknown',
- 'controller' => 'featured_tile'
- }
- })
- assert_response 500
- assert_match(/RuntimeError\n in FeaturedTileController/, body)
- end
-
test "sets the HTTP charset parameter" do
- @app = DevelopmentApp
+ @app = ProductionApp
get "/", {}, {'action_dispatch.show_exceptions' => true}
assert_equal "text/html; charset=utf-8", response.headers["Content-Type"]
end
- test 'uses logger from env' do
+ test "show registered original exception for wrapped exceptions" do
@app = ProductionApp
- output = StringIO.new
- get "/", {}, {'action_dispatch.show_exceptions' => true, 'action_dispatch.logger' => Logger.new(output)}
- assert_match(/puke/, output.rewind && output.read)
- end
- test 'uses backtrace cleaner from env' do
- @app = DevelopmentApp
- cleaner = stub(:clean => ['passed backtrace cleaner'])
- get "/", {}, {'action_dispatch.show_exceptions' => true, 'action_dispatch.backtrace_cleaner' => cleaner}
- assert_match(/passed backtrace cleaner/, body)
+ get "/not_found_original_exception", {}, {'action_dispatch.show_exceptions' => true}
+ assert_response 404
+ assert_match(/404 error/, body)
end
end
diff --git a/activerecord/lib/active_record/railtie.rb b/activerecord/lib/active_record/railtie.rb
index 47133e77e8..c2e31579a4 100644
--- a/activerecord/lib/active_record/railtie.rb
+++ b/activerecord/lib/active_record/railtie.rb
@@ -22,6 +22,13 @@ module ActiveRecord
config.app_middleware.insert_after "::ActionDispatch::Callbacks",
"ActiveRecord::ConnectionAdapters::ConnectionManagement"
+ config.action_dispatch.rescue_responses.merge!(
+ 'ActiveRecord::RecordNotFound' => :not_found,
+ 'ActiveRecord::StaleObjectError' => :conflict,
+ 'ActiveRecord::RecordInvalid' => :unprocessable_entity,
+ 'ActiveRecord::RecordNotSaved' => :unprocessable_entity
+ )
+
rake_tasks do
load "active_record/railties/databases.rake"
end
diff --git a/railties/CHANGELOG.md b/railties/CHANGELOG.md
index 6b0be4c096..d158fdd46f 100644
--- a/railties/CHANGELOG.md
+++ b/railties/CHANGELOG.md
@@ -1,13 +1,15 @@
## Rails 3.2.0 (unreleased) ##
-* Display mounted engine's routes in `rake routes`. *Piotr Sarnacki*
+* Add DebugExceptions middleware which contains features extracted from ShowExceptions middleware *José Valim*
+
+* Display mounted engine's routes in `rake routes` *Piotr Sarnacki*
* Allow to change the loading order of railties with `config.railties_order=` *Piotr Sarnacki*
Example:
config.railties_order = [Blog::Engine, :main_app, :all]
-* Scaffold returns 204 No Content for API requests without content. This makes scaffold work with jQuery out of the box. *José Valim*
+* Scaffold returns 204 No Content for API requests without content. This makes scaffold work with jQuery out of the box *José Valim*
* Update Rails::Rack::Logger middleware to apply any tags set in config.log_tags to the newly ActiveSupport::TaggedLogging Rails.logger. This makes it easy to tag log lines with debug information like subdomain and request id -- both very helpful in debugging multi-user production applications *DHH*
diff --git a/railties/lib/rails/application.rb b/railties/lib/rails/application.rb
index 2a62446a04..f5f47acfbc 100644
--- a/railties/lib/rails/application.rb
+++ b/railties/lib/rails/application.rb
@@ -195,10 +195,13 @@ module Rails
middleware.use ::ActionDispatch::RequestId
middleware.use ::Rails::Rack::Logger, config.log_tags # must come after Rack::MethodOverride to properly log overridden methods
middleware.use ::ActionDispatch::ShowExceptions
+ middleware.use ::ActionDispatch::DebugExceptions
middleware.use ::ActionDispatch::RemoteIp, config.action_dispatch.ip_spoofing_check, config.action_dispatch.trusted_proxies
+
if config.action_dispatch.x_sendfile_header.present?
middleware.use ::Rack::Sendfile, config.action_dispatch.x_sendfile_header
end
+
middleware.use ::ActionDispatch::Reloader unless config.cache_classes
middleware.use ::ActionDispatch::Callbacks
middleware.use ::ActionDispatch::Cookies
diff --git a/railties/test/application/middleware/show_exceptions_test.rb b/railties/test/application/middleware/exceptions_test.rb
index 7dbadc6ce3..0174352900 100644
--- a/railties/test/application/middleware/show_exceptions_test.rb
+++ b/railties/test/application/middleware/exceptions_test.rb
@@ -3,7 +3,7 @@ require 'isolation/abstract_unit'
require 'rack/test'
module ApplicationTests
- class ShowExceptionsTest < Test::Unit::TestCase
+ class MiddlewareExceptionsTest < Test::Unit::TestCase
include ActiveSupport::Testing::Isolation
include Rack::Test::Methods
@@ -16,6 +16,35 @@ module ApplicationTests
teardown_app
end
+ test "show exceptions middleware filter backtrace before logging" do
+ my_middleware = Struct.new(:app) do
+ def call(env)
+ raise "Failure"
+ end
+ end
+
+ app.config.middleware.use my_middleware
+
+ stringio = StringIO.new
+ Rails.logger = Logger.new(stringio)
+
+ get "/"
+ assert_no_match(/action_dispatch/, stringio.string)
+ end
+
+ test "renders active record exceptions as 404" do
+ my_middleware = Struct.new(:app) do
+ def call(env)
+ raise ActiveRecord::RecordNotFound
+ end
+ end
+
+ app.config.middleware.use my_middleware
+
+ get "/"
+ assert_equal 404, last_response.status
+ end
+
test "unspecified route when set action_dispatch.show_exceptions to false" do
app.config.action_dispatch.show_exceptions = false
diff --git a/railties/test/application/middleware_test.rb b/railties/test/application/middleware_test.rb
index 4703a59326..578370cfca 100644
--- a/railties/test/application/middleware_test.rb
+++ b/railties/test/application/middleware_test.rb
@@ -33,6 +33,7 @@ module ApplicationTests
"ActionDispatch::RequestId",
"Rails::Rack::Logger", # must come after Rack::MethodOverride to properly log overridden methods
"ActionDispatch::ShowExceptions",
+ "ActionDispatch::DebugExceptions",
"ActionDispatch::RemoteIp",
"Rack::Sendfile",
"ActionDispatch::Reloader",
@@ -104,10 +105,11 @@ module ApplicationTests
assert !middleware.include?("ActionDispatch::Static")
end
- test "includes show exceptions even action_dispatch.show_exceptions is disabled" do
+ test "includes exceptions middlewares even if action_dispatch.show_exceptions is disabled" do
add_to_config "config.action_dispatch.show_exceptions = false"
boot!
assert middleware.include?("ActionDispatch::ShowExceptions")
+ assert middleware.include?("ActionDispatch::DebugExceptions")
end
test "removes ActionDispatch::Reloader if cache_classes is true" do
@@ -191,26 +193,6 @@ module ApplicationTests
assert_equal nil, last_response.headers["Etag"]
end
- # Show exceptions middleware
- test "show exceptions middleware filter backtrace before logging" do
- my_middleware = Struct.new(:app) do
- def call(env)
- raise "Failure"
- end
- end
-
- make_basic_app do |app|
- app.config.middleware.use my_middleware
- end
-
- stringio = StringIO.new
- Rails.logger = Logger.new(stringio)
-
- env = Rack::MockRequest.env_for("/")
- Rails.application.call(env)
- assert_no_match(/action_dispatch/, stringio.string)
- end
-
private
def boot!