Merge pull request #3717 from lest/show-exceptions-refactor
Show exceptions refactor: controller should be responsible for choice to show exceptions
This commit is contained in:
commit
39ecbfdab9
@ -1,5 +1,9 @@
|
|||||||
## Rails 3.2.0 (unreleased) ##
|
## Rails 3.2.0 (unreleased) ##
|
||||||
|
|
||||||
|
* Refactor ActionDispatch::ShowExceptions. Controller is responsible for choice to show exceptions. *Sergey Nartimov*
|
||||||
|
|
||||||
|
It's possible to override +show_detailed_exceptions?+ in controllers to specify which requests should provide debugging information on errors.
|
||||||
|
|
||||||
* Responders now return 204 No Content for API requests without a response body (as in the new scaffold) *José Valim*
|
* Responders now return 204 No Content for API requests without a response body (as in the new scaffold) *José Valim*
|
||||||
|
|
||||||
* Added ActionDispatch::RequestId middleware that'll make a unique X-Request-Id header available to the response and enables the ActionDispatch::Request#uuid method. This makes it easy to trace requests from end-to-end in the stack and to identify individual requests in mixed logs like Syslog *DHH*
|
* Added ActionDispatch::RequestId middleware that'll make a unique X-Request-Id header available to the response and enables the ActionDispatch::Request#uuid method. This makes it easy to trace requests from end-to-end in the stack and to identify individual requests in mixed logs like Syslog *DHH*
|
||||||
|
@ -3,6 +3,11 @@ module Rescue
|
|||||||
extend ActiveSupport::Concern
|
extend ActiveSupport::Concern
|
||||||
include ActiveSupport::Rescuable
|
include ActiveSupport::Rescuable
|
||||||
|
|
||||||
|
included do
|
||||||
|
config_accessor :consider_all_requests_local
|
||||||
|
self.consider_all_requests_local = false if consider_all_requests_local.nil?
|
||||||
|
end
|
||||||
|
|
||||||
def rescue_with_handler(exception)
|
def rescue_with_handler(exception)
|
||||||
if (exception.respond_to?(:original_exception) &&
|
if (exception.respond_to?(:original_exception) &&
|
||||||
(orig_exception = exception.original_exception) &&
|
(orig_exception = exception.original_exception) &&
|
||||||
@ -12,10 +17,15 @@ def rescue_with_handler(exception)
|
|||||||
super(exception)
|
super(exception)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def show_detailed_exceptions?
|
||||||
|
consider_all_requests_local || request.local?
|
||||||
|
end
|
||||||
|
|
||||||
private
|
private
|
||||||
def process_action(*args)
|
def process_action(*args)
|
||||||
super
|
super
|
||||||
rescue Exception => exception
|
rescue Exception => exception
|
||||||
|
request.env['action_dispatch.show_detailed_exceptions'] = show_detailed_exceptions?
|
||||||
rescue_with_handler(exception) || raise(exception)
|
rescue_with_handler(exception) || raise(exception)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
@ -21,6 +21,8 @@ class Railtie < Rails::Railtie
|
|||||||
paths = app.config.paths
|
paths = app.config.paths
|
||||||
options = app.config.action_controller
|
options = app.config.action_controller
|
||||||
|
|
||||||
|
options.consider_all_requests_local ||= app.config.consider_all_requests_local
|
||||||
|
|
||||||
options.assets_dir ||= paths["public"].first
|
options.assets_dir ||= paths["public"].first
|
||||||
options.javascripts_dir ||= paths["public/javascripts"].first
|
options.javascripts_dir ||= paths["public/javascripts"].first
|
||||||
options.stylesheets_dir ||= paths["public/stylesheets"].first
|
options.stylesheets_dir ||= paths["public/stylesheets"].first
|
||||||
|
@ -2,6 +2,7 @@
|
|||||||
require 'action_controller/metal/exceptions'
|
require 'action_controller/metal/exceptions'
|
||||||
require 'active_support/notifications'
|
require 'active_support/notifications'
|
||||||
require 'action_dispatch/http/request'
|
require 'action_dispatch/http/request'
|
||||||
|
require 'active_support/deprecation'
|
||||||
|
|
||||||
module ActionDispatch
|
module ActionDispatch
|
||||||
# This middleware rescues any exception returned by the application and renders
|
# This middleware rescues any exception returned by the application and renders
|
||||||
@ -38,9 +39,9 @@ class ShowExceptions
|
|||||||
"application's log file and/or the web server's log file to find out what " <<
|
"application's log file and/or the web server's log file to find out what " <<
|
||||||
"went wrong.</body></html>"]]
|
"went wrong.</body></html>"]]
|
||||||
|
|
||||||
def initialize(app, consider_all_requests_local = false)
|
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
|
@app = app
|
||||||
@consider_all_requests_local = consider_all_requests_local
|
|
||||||
end
|
end
|
||||||
|
|
||||||
def call(env)
|
def call(env)
|
||||||
@ -65,11 +66,10 @@ def render_exception(env, exception)
|
|||||||
log_error(exception)
|
log_error(exception)
|
||||||
exception = original_exception(exception)
|
exception = original_exception(exception)
|
||||||
|
|
||||||
request = Request.new(env)
|
if env['action_dispatch.show_detailed_exceptions'] == true
|
||||||
if @consider_all_requests_local || request.local?
|
rescue_action_diagnostics(env, exception)
|
||||||
rescue_action_locally(request, exception)
|
|
||||||
else
|
else
|
||||||
rescue_action_in_public(exception)
|
rescue_action_error_page(exception)
|
||||||
end
|
end
|
||||||
rescue Exception => failsafe_error
|
rescue Exception => failsafe_error
|
||||||
$stderr.puts "Error during failsafe response: #{failsafe_error}\n #{failsafe_error.backtrace * "\n "}"
|
$stderr.puts "Error during failsafe response: #{failsafe_error}\n #{failsafe_error.backtrace * "\n "}"
|
||||||
@ -78,9 +78,9 @@ def render_exception(env, exception)
|
|||||||
|
|
||||||
# Render detailed diagnostics for unhandled exceptions rescued from
|
# Render detailed diagnostics for unhandled exceptions rescued from
|
||||||
# a controller action.
|
# a controller action.
|
||||||
def rescue_action_locally(request, exception)
|
def rescue_action_diagnostics(env, exception)
|
||||||
template = ActionView::Base.new([RESCUES_TEMPLATE_PATH],
|
template = ActionView::Base.new([RESCUES_TEMPLATE_PATH],
|
||||||
:request => request,
|
:request => Request.new(env),
|
||||||
:exception => exception,
|
:exception => exception,
|
||||||
:application_trace => application_trace(exception),
|
:application_trace => application_trace(exception),
|
||||||
:framework_trace => framework_trace(exception),
|
:framework_trace => framework_trace(exception),
|
||||||
@ -98,7 +98,7 @@ def rescue_action_locally(request, exception)
|
|||||||
# it will first attempt to render the file at <tt>public/500.da.html</tt>
|
# 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,
|
# then attempt to render <tt>public/500.html</tt>. If none of them exist,
|
||||||
# the body of the response will be left empty.
|
# the body of the response will be left empty.
|
||||||
def rescue_action_in_public(exception)
|
def rescue_action_error_page(exception)
|
||||||
status = status_code(exception)
|
status = status_code(exception)
|
||||||
locale_path = "#{public_path}/#{status}.#{I18n.locale}.html" if I18n.locale
|
locale_path = "#{public_path}/#{status}.#{I18n.locale}.html" if I18n.locale
|
||||||
path = "#{public_path}/#{status}.html"
|
path = "#{public_path}/#{status}.html"
|
||||||
|
59
actionpack/test/controller/show_exceptions_test.rb
Normal file
59
actionpack/test/controller/show_exceptions_test.rb
Normal file
@ -0,0 +1,59 @@
|
|||||||
|
require 'abstract_unit'
|
||||||
|
|
||||||
|
module ShowExceptions
|
||||||
|
class ShowExceptionsController < ActionController::Base
|
||||||
|
use ActionDispatch::ShowExceptions
|
||||||
|
|
||||||
|
def boom
|
||||||
|
raise 'boom!'
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
class ShowExceptionsTest < ActionDispatch::IntegrationTest
|
||||||
|
test 'show error page from a remote ip' do
|
||||||
|
@app = ShowExceptionsController.action(:boom)
|
||||||
|
self.remote_addr = '208.77.188.166'
|
||||||
|
get '/'
|
||||||
|
assert_equal "500 error fixture\n", body
|
||||||
|
end
|
||||||
|
|
||||||
|
test 'show diagnostics from a local ip' do
|
||||||
|
@app = ShowExceptionsController.action(:boom)
|
||||||
|
['127.0.0.1', '127.0.0.127', '::1', '0:0:0:0:0:0:0:1', '0:0:0:0:0:0:0:1%0'].each do |ip_address|
|
||||||
|
self.remote_addr = ip_address
|
||||||
|
get '/'
|
||||||
|
assert_match /boom/, body
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
test 'show diagnostics from a remote ip when consider_all_requests_local is true' do
|
||||||
|
ShowExceptionsController.any_instance.stubs(:consider_all_requests_local).returns(true)
|
||||||
|
@app = ShowExceptionsController.action(:boom)
|
||||||
|
self.remote_addr = '208.77.188.166'
|
||||||
|
get '/'
|
||||||
|
assert_match /boom/, body
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
class ShowExceptionsOverridenController < ShowExceptionsController
|
||||||
|
private
|
||||||
|
|
||||||
|
def show_detailed_exceptions?
|
||||||
|
params['detailed'] == '1'
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
class ShowExceptionsOverridenTest < ActionDispatch::IntegrationTest
|
||||||
|
test 'show error page' do
|
||||||
|
@app = ShowExceptionsOverridenController.action(:boom)
|
||||||
|
get '/', {'detailed' => '0'}
|
||||||
|
assert_equal "500 error fixture\n", body
|
||||||
|
end
|
||||||
|
|
||||||
|
test 'show diagnostics message' do
|
||||||
|
@app = ShowExceptionsOverridenController.action(:boom)
|
||||||
|
get '/', {'detailed' => '1'}
|
||||||
|
assert_match /boom/, body
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
@ -2,7 +2,13 @@
|
|||||||
|
|
||||||
class ShowExceptionsTest < ActionDispatch::IntegrationTest
|
class ShowExceptionsTest < ActionDispatch::IntegrationTest
|
||||||
|
|
||||||
Boomer = lambda do |env|
|
class Boomer
|
||||||
|
def initialize(detailed = false)
|
||||||
|
@detailed = detailed
|
||||||
|
end
|
||||||
|
|
||||||
|
def call(env)
|
||||||
|
env['action_dispatch.show_detailed_exceptions'] = @detailed
|
||||||
req = ActionDispatch::Request.new(env)
|
req = ActionDispatch::Request.new(env)
|
||||||
case req.path
|
case req.path
|
||||||
when "/not_found"
|
when "/not_found"
|
||||||
@ -21,13 +27,13 @@ class ShowExceptionsTest < ActionDispatch::IntegrationTest
|
|||||||
raise "puke!"
|
raise "puke!"
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
end
|
||||||
|
|
||||||
ProductionApp = ActionDispatch::ShowExceptions.new(Boomer, false)
|
ProductionApp = ActionDispatch::ShowExceptions.new(Boomer.new(false))
|
||||||
DevelopmentApp = ActionDispatch::ShowExceptions.new(Boomer, true)
|
DevelopmentApp = ActionDispatch::ShowExceptions.new(Boomer.new(true))
|
||||||
|
|
||||||
test "rescue in public from a remote ip" do
|
test "rescue with error page when show_exceptions is false" do
|
||||||
@app = ProductionApp
|
@app = ProductionApp
|
||||||
self.remote_addr = '208.77.188.166'
|
|
||||||
|
|
||||||
get "/", {}, {'action_dispatch.show_exceptions' => true}
|
get "/", {}, {'action_dispatch.show_exceptions' => true}
|
||||||
assert_response 500
|
assert_response 500
|
||||||
@ -42,10 +48,8 @@ class ShowExceptionsTest < ActionDispatch::IntegrationTest
|
|||||||
assert_equal "", body
|
assert_equal "", body
|
||||||
end
|
end
|
||||||
|
|
||||||
test "rescue locally from a local request" do
|
test "rescue with diagnostics message when show_exceptions is true" do
|
||||||
@app = ProductionApp
|
@app = DevelopmentApp
|
||||||
['127.0.0.1', '127.0.0.127', '::1', '0:0:0:0:0:0:0:1', '0:0:0:0:0:0:0:1%0'].each do |ip_address|
|
|
||||||
self.remote_addr = ip_address
|
|
||||||
|
|
||||||
get "/", {}, {'action_dispatch.show_exceptions' => true}
|
get "/", {}, {'action_dispatch.show_exceptions' => true}
|
||||||
assert_response 500
|
assert_response 500
|
||||||
@ -59,15 +63,13 @@ class ShowExceptionsTest < ActionDispatch::IntegrationTest
|
|||||||
assert_response 405
|
assert_response 405
|
||||||
assert_match(/ActionController::MethodNotAllowed/, body)
|
assert_match(/ActionController::MethodNotAllowed/, body)
|
||||||
end
|
end
|
||||||
end
|
|
||||||
|
|
||||||
test "localize public rescue message" do
|
test "localize rescue error page" do
|
||||||
# Change locale
|
# Change locale
|
||||||
old_locale, I18n.locale = I18n.locale, :da
|
old_locale, I18n.locale = I18n.locale, :da
|
||||||
|
|
||||||
begin
|
begin
|
||||||
@app = ProductionApp
|
@app = ProductionApp
|
||||||
self.remote_addr = '208.77.188.166'
|
|
||||||
|
|
||||||
get "/", {}, {'action_dispatch.show_exceptions' => true}
|
get "/", {}, {'action_dispatch.show_exceptions' => true}
|
||||||
assert_response 500
|
assert_response 500
|
||||||
@ -81,23 +83,6 @@ class ShowExceptionsTest < ActionDispatch::IntegrationTest
|
|||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
test "always rescue locally in development mode" do
|
|
||||||
@app = DevelopmentApp
|
|
||||||
self.remote_addr = '208.77.188.166'
|
|
||||||
|
|
||||||
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
|
test "does not show filtered parameters" do
|
||||||
@app = DevelopmentApp
|
@app = DevelopmentApp
|
||||||
|
|
||||||
@ -107,16 +92,15 @@ class ShowExceptionsTest < ActionDispatch::IntegrationTest
|
|||||||
assert_match(""foo"=>"[FILTERED]"", body)
|
assert_match(""foo"=>"[FILTERED]"", body)
|
||||||
end
|
end
|
||||||
|
|
||||||
test "show registered original exception for wrapped exceptions when consider_all_requests_local is false" do
|
test "show registered original exception for wrapped exceptions when show_exceptions is false" do
|
||||||
@app = ProductionApp
|
@app = ProductionApp
|
||||||
self.remote_addr = '208.77.188.166'
|
|
||||||
|
|
||||||
get "/not_found_original_exception", {}, {'action_dispatch.show_exceptions' => true}
|
get "/not_found_original_exception", {}, {'action_dispatch.show_exceptions' => true}
|
||||||
assert_response 404
|
assert_response 404
|
||||||
assert_match(/404 error/, body)
|
assert_match(/404 error/, body)
|
||||||
end
|
end
|
||||||
|
|
||||||
test "show registered original exception for wrapped exceptions when consider_all_requests_local is true" do
|
test "show registered original exception for wrapped exceptions when show_exceptions is true" do
|
||||||
@app = DevelopmentApp
|
@app = DevelopmentApp
|
||||||
|
|
||||||
get "/not_found_original_exception", {}, {'action_dispatch.show_exceptions' => true}
|
get "/not_found_original_exception", {}, {'action_dispatch.show_exceptions' => true}
|
||||||
@ -125,7 +109,7 @@ class ShowExceptionsTest < ActionDispatch::IntegrationTest
|
|||||||
end
|
end
|
||||||
|
|
||||||
test "show the controller name in the diagnostics template when controller name is present" do
|
test "show the controller name in the diagnostics template when controller name is present" do
|
||||||
@app = ProductionApp
|
@app = DevelopmentApp
|
||||||
get("/runtime_error", {}, {
|
get("/runtime_error", {}, {
|
||||||
'action_dispatch.show_exceptions' => true,
|
'action_dispatch.show_exceptions' => true,
|
||||||
'action_dispatch.request.parameters' => {
|
'action_dispatch.request.parameters' => {
|
||||||
|
@ -166,7 +166,7 @@ def default_middleware_stack
|
|||||||
middleware.use ::Rack::MethodOverride
|
middleware.use ::Rack::MethodOverride
|
||||||
middleware.use ::ActionDispatch::RequestId
|
middleware.use ::ActionDispatch::RequestId
|
||||||
middleware.use ::Rails::Rack::Logger, config.log_tags # must come after Rack::MethodOverride to properly log overridden methods
|
middleware.use ::Rails::Rack::Logger, config.log_tags # must come after Rack::MethodOverride to properly log overridden methods
|
||||||
middleware.use ::ActionDispatch::ShowExceptions, config.consider_all_requests_local
|
middleware.use ::ActionDispatch::ShowExceptions
|
||||||
middleware.use ::ActionDispatch::RemoteIp, config.action_dispatch.ip_spoofing_check, config.action_dispatch.trusted_proxies
|
middleware.use ::ActionDispatch::RemoteIp, config.action_dispatch.ip_spoofing_check, config.action_dispatch.trusted_proxies
|
||||||
if config.action_dispatch.x_sendfile_header.present?
|
if config.action_dispatch.x_sendfile_header.present?
|
||||||
middleware.use ::Rack::Sendfile, config.action_dispatch.x_sendfile_header
|
middleware.use ::Rack::Sendfile, config.action_dispatch.x_sendfile_header
|
||||||
|
Loading…
Reference in New Issue
Block a user