Skip to content

Commit 39ecbfd

Browse files
committed
Merge pull request rails#3717 from lest/show-exceptions-refactor
Show exceptions refactor: controller should be responsible for choice to show exceptions
2 parents 8cae31c + 3a1d519 commit 39ecbfd

File tree

7 files changed

+127
-68
lines changed

7 files changed

+127
-68
lines changed

actionpack/CHANGELOG.md

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,9 @@
11
## Rails 3.2.0 (unreleased) ##
22

3+
* Refactor ActionDispatch::ShowExceptions. Controller is responsible for choice to show exceptions. *Sergey Nartimov*
4+
5+
It's possible to override +show_detailed_exceptions?+ in controllers to specify which requests should provide debugging information on errors.
6+
37
* Responders now return 204 No Content for API requests without a response body (as in the new scaffold) *José Valim*
48

59
* 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*

actionpack/lib/action_controller/metal/rescue.rb

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,11 @@ module Rescue
33
extend ActiveSupport::Concern
44
include ActiveSupport::Rescuable
55

6+
included do
7+
config_accessor :consider_all_requests_local
8+
self.consider_all_requests_local = false if consider_all_requests_local.nil?
9+
end
10+
611
def rescue_with_handler(exception)
712
if (exception.respond_to?(:original_exception) &&
813
(orig_exception = exception.original_exception) &&
@@ -12,10 +17,15 @@ def rescue_with_handler(exception)
1217
super(exception)
1318
end
1419

20+
def show_detailed_exceptions?
21+
consider_all_requests_local || request.local?
22+
end
23+
1524
private
1625
def process_action(*args)
1726
super
1827
rescue Exception => exception
28+
request.env['action_dispatch.show_detailed_exceptions'] = show_detailed_exceptions?
1929
rescue_with_handler(exception) || raise(exception)
2030
end
2131
end

actionpack/lib/action_controller/railtie.rb

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,8 @@ class Railtie < Rails::Railtie
2121
paths = app.config.paths
2222
options = app.config.action_controller
2323

24+
options.consider_all_requests_local ||= app.config.consider_all_requests_local
25+
2426
options.assets_dir ||= paths["public"].first
2527
options.javascripts_dir ||= paths["public/javascripts"].first
2628
options.stylesheets_dir ||= paths["public/stylesheets"].first

actionpack/lib/action_dispatch/middleware/show_exceptions.rb

Lines changed: 9 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22
require 'action_controller/metal/exceptions'
33
require 'active_support/notifications'
44
require 'action_dispatch/http/request'
5+
require 'active_support/deprecation'
56

67
module ActionDispatch
78
# This middleware rescues any exception returned by the application and renders
@@ -38,9 +39,9 @@ class ShowExceptions
3839
"application's log file and/or the web server's log file to find out what " <<
3940
"went wrong.</body></html>"]]
4041

41-
def initialize(app, consider_all_requests_local = false)
42+
def initialize(app, consider_all_requests_local = nil)
43+
ActiveSupport::Deprecation.warn "Passing consider_all_requests_local option to ActionDispatch::ShowExceptions middleware no longer works" unless consider_all_requests_local.nil?
4244
@app = app
43-
@consider_all_requests_local = consider_all_requests_local
4445
end
4546

4647
def call(env)
@@ -65,11 +66,10 @@ def render_exception(env, exception)
6566
log_error(exception)
6667
exception = original_exception(exception)
6768

68-
request = Request.new(env)
69-
if @consider_all_requests_local || request.local?
70-
rescue_action_locally(request, exception)
69+
if env['action_dispatch.show_detailed_exceptions'] == true
70+
rescue_action_diagnostics(env, exception)
7171
else
72-
rescue_action_in_public(exception)
72+
rescue_action_error_page(exception)
7373
end
7474
rescue Exception => failsafe_error
7575
$stderr.puts "Error during failsafe response: #{failsafe_error}\n #{failsafe_error.backtrace * "\n "}"
@@ -78,9 +78,9 @@ def render_exception(env, exception)
7878

7979
# Render detailed diagnostics for unhandled exceptions rescued from
8080
# a controller action.
81-
def rescue_action_locally(request, exception)
81+
def rescue_action_diagnostics(env, exception)
8282
template = ActionView::Base.new([RESCUES_TEMPLATE_PATH],
83-
:request => request,
83+
:request => Request.new(env),
8484
:exception => exception,
8585
:application_trace => application_trace(exception),
8686
:framework_trace => framework_trace(exception),
@@ -98,7 +98,7 @@ def rescue_action_locally(request, exception)
9898
# it will first attempt to render the file at <tt>public/500.da.html</tt>
9999
# then attempt to render <tt>public/500.html</tt>. If none of them exist,
100100
# the body of the response will be left empty.
101-
def rescue_action_in_public(exception)
101+
def rescue_action_error_page(exception)
102102
status = status_code(exception)
103103
locale_path = "#{public_path}/#{status}.#{I18n.locale}.html" if I18n.locale
104104
path = "#{public_path}/#{status}.html"
Lines changed: 59 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,59 @@
1+
require 'abstract_unit'
2+
3+
module ShowExceptions
4+
class ShowExceptionsController < ActionController::Base
5+
use ActionDispatch::ShowExceptions
6+
7+
def boom
8+
raise 'boom!'
9+
end
10+
end
11+
12+
class ShowExceptionsTest < ActionDispatch::IntegrationTest
13+
test 'show error page from a remote ip' do
14+
@app = ShowExceptionsController.action(:boom)
15+
self.remote_addr = '208.77.188.166'
16+
get '/'
17+
assert_equal "500 error fixture\n", body
18+
end
19+
20+
test 'show diagnostics from a local ip' do
21+
@app = ShowExceptionsController.action(:boom)
22+
['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|
23+
self.remote_addr = ip_address
24+
get '/'
25+
assert_match /boom/, body
26+
end
27+
end
28+
29+
test 'show diagnostics from a remote ip when consider_all_requests_local is true' do
30+
ShowExceptionsController.any_instance.stubs(:consider_all_requests_local).returns(true)
31+
@app = ShowExceptionsController.action(:boom)
32+
self.remote_addr = '208.77.188.166'
33+
get '/'
34+
assert_match /boom/, body
35+
end
36+
end
37+
38+
class ShowExceptionsOverridenController < ShowExceptionsController
39+
private
40+
41+
def show_detailed_exceptions?
42+
params['detailed'] == '1'
43+
end
44+
end
45+
46+
class ShowExceptionsOverridenTest < ActionDispatch::IntegrationTest
47+
test 'show error page' do
48+
@app = ShowExceptionsOverridenController.action(:boom)
49+
get '/', {'detailed' => '0'}
50+
assert_equal "500 error fixture\n", body
51+
end
52+
53+
test 'show diagnostics message' do
54+
@app = ShowExceptionsOverridenController.action(:boom)
55+
get '/', {'detailed' => '1'}
56+
assert_match /boom/, body
57+
end
58+
end
59+
end

actionpack/test/dispatch/show_exceptions_test.rb

Lines changed: 42 additions & 58 deletions
Original file line numberDiff line numberDiff line change
@@ -2,32 +2,38 @@
22

33
class ShowExceptionsTest < ActionDispatch::IntegrationTest
44

5-
Boomer = lambda do |env|
6-
req = ActionDispatch::Request.new(env)
7-
case req.path
8-
when "/not_found"
9-
raise ActionController::UnknownAction
10-
when "/runtime_error"
11-
raise RuntimeError
12-
when "/method_not_allowed"
13-
raise ActionController::MethodNotAllowed
14-
when "/not_implemented"
15-
raise ActionController::NotImplemented
16-
when "/unprocessable_entity"
17-
raise ActionController::InvalidAuthenticityToken
18-
when "/not_found_original_exception"
19-
raise ActionView::Template::Error.new('template', {}, AbstractController::ActionNotFound.new)
20-
else
21-
raise "puke!"
5+
class Boomer
6+
def initialize(detailed = false)
7+
@detailed = detailed
8+
end
9+
10+
def call(env)
11+
env['action_dispatch.show_detailed_exceptions'] = @detailed
12+
req = ActionDispatch::Request.new(env)
13+
case req.path
14+
when "/not_found"
15+
raise ActionController::UnknownAction
16+
when "/runtime_error"
17+
raise RuntimeError
18+
when "/method_not_allowed"
19+
raise ActionController::MethodNotAllowed
20+
when "/not_implemented"
21+
raise ActionController::NotImplemented
22+
when "/unprocessable_entity"
23+
raise ActionController::InvalidAuthenticityToken
24+
when "/not_found_original_exception"
25+
raise ActionView::Template::Error.new('template', {}, AbstractController::ActionNotFound.new)
26+
else
27+
raise "puke!"
28+
end
2229
end
2330
end
2431

25-
ProductionApp = ActionDispatch::ShowExceptions.new(Boomer, false)
26-
DevelopmentApp = ActionDispatch::ShowExceptions.new(Boomer, true)
32+
ProductionApp = ActionDispatch::ShowExceptions.new(Boomer.new(false))
33+
DevelopmentApp = ActionDispatch::ShowExceptions.new(Boomer.new(true))
2734

28-
test "rescue in public from a remote ip" do
35+
test "rescue with error page when show_exceptions is false" do
2936
@app = ProductionApp
30-
self.remote_addr = '208.77.188.166'
3137

3238
get "/", {}, {'action_dispatch.show_exceptions' => true}
3339
assert_response 500
@@ -42,32 +48,28 @@ class ShowExceptionsTest < ActionDispatch::IntegrationTest
4248
assert_equal "", body
4349
end
4450

45-
test "rescue locally from a local request" do
46-
@app = ProductionApp
47-
['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|
48-
self.remote_addr = ip_address
51+
test "rescue with diagnostics message when show_exceptions is true" do
52+
@app = DevelopmentApp
4953

50-
get "/", {}, {'action_dispatch.show_exceptions' => true}
51-
assert_response 500
52-
assert_match(/puke/, body)
54+
get "/", {}, {'action_dispatch.show_exceptions' => true}
55+
assert_response 500
56+
assert_match(/puke/, body)
5357

54-
get "/not_found", {}, {'action_dispatch.show_exceptions' => true}
55-
assert_response 404
56-
assert_match(/#{ActionController::UnknownAction.name}/, body)
58+
get "/not_found", {}, {'action_dispatch.show_exceptions' => true}
59+
assert_response 404
60+
assert_match(/#{ActionController::UnknownAction.name}/, body)
5761

58-
get "/method_not_allowed", {}, {'action_dispatch.show_exceptions' => true}
59-
assert_response 405
60-
assert_match(/ActionController::MethodNotAllowed/, body)
61-
end
62+
get "/method_not_allowed", {}, {'action_dispatch.show_exceptions' => true}
63+
assert_response 405
64+
assert_match(/ActionController::MethodNotAllowed/, body)
6265
end
6366

64-
test "localize public rescue message" do
67+
test "localize rescue error page" do
6568
# Change locale
6669
old_locale, I18n.locale = I18n.locale, :da
6770

6871
begin
6972
@app = ProductionApp
70-
self.remote_addr = '208.77.188.166'
7173

7274
get "/", {}, {'action_dispatch.show_exceptions' => true}
7375
assert_response 500
@@ -81,23 +83,6 @@ class ShowExceptionsTest < ActionDispatch::IntegrationTest
8183
end
8284
end
8385

84-
test "always rescue locally in development mode" do
85-
@app = DevelopmentApp
86-
self.remote_addr = '208.77.188.166'
87-
88-
get "/", {}, {'action_dispatch.show_exceptions' => true}
89-
assert_response 500
90-
assert_match(/puke/, body)
91-
92-
get "/not_found", {}, {'action_dispatch.show_exceptions' => true}
93-
assert_response 404
94-
assert_match(/#{ActionController::UnknownAction.name}/, body)
95-
96-
get "/method_not_allowed", {}, {'action_dispatch.show_exceptions' => true}
97-
assert_response 405
98-
assert_match(/ActionController::MethodNotAllowed/, body)
99-
end
100-
10186
test "does not show filtered parameters" do
10287
@app = DevelopmentApp
10388

@@ -107,16 +92,15 @@ class ShowExceptionsTest < ActionDispatch::IntegrationTest
10792
assert_match("&quot;foo&quot;=&gt;&quot;[FILTERED]&quot;", body)
10893
end
10994

110-
test "show registered original exception for wrapped exceptions when consider_all_requests_local is false" do
95+
test "show registered original exception for wrapped exceptions when show_exceptions is false" do
11196
@app = ProductionApp
112-
self.remote_addr = '208.77.188.166'
11397

11498
get "/not_found_original_exception", {}, {'action_dispatch.show_exceptions' => true}
11599
assert_response 404
116100
assert_match(/404 error/, body)
117101
end
118102

119-
test "show registered original exception for wrapped exceptions when consider_all_requests_local is true" do
103+
test "show registered original exception for wrapped exceptions when show_exceptions is true" do
120104
@app = DevelopmentApp
121105

122106
get "/not_found_original_exception", {}, {'action_dispatch.show_exceptions' => true}
@@ -125,7 +109,7 @@ class ShowExceptionsTest < ActionDispatch::IntegrationTest
125109
end
126110

127111
test "show the controller name in the diagnostics template when controller name is present" do
128-
@app = ProductionApp
112+
@app = DevelopmentApp
129113
get("/runtime_error", {}, {
130114
'action_dispatch.show_exceptions' => true,
131115
'action_dispatch.request.parameters' => {

railties/lib/rails/application.rb

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -166,7 +166,7 @@ def default_middleware_stack
166166
middleware.use ::Rack::MethodOverride
167167
middleware.use ::ActionDispatch::RequestId
168168
middleware.use ::Rails::Rack::Logger, config.log_tags # must come after Rack::MethodOverride to properly log overridden methods
169-
middleware.use ::ActionDispatch::ShowExceptions, config.consider_all_requests_local
169+
middleware.use ::ActionDispatch::ShowExceptions
170170
middleware.use ::ActionDispatch::RemoteIp, config.action_dispatch.ip_spoofing_check, config.action_dispatch.trusted_proxies
171171
if config.action_dispatch.x_sendfile_header.present?
172172
middleware.use ::Rack::Sendfile, config.action_dispatch.x_sendfile_header

0 commit comments

Comments
 (0)