1
0
Fork 0
mirror of https://github.com/rails/rails.git synced 2022-11-09 12:12:34 -05:00

Introduce ActionDispatch::DebugExceptions interceptors

Plugins interacting with the exceptions caught and displayed by
ActionDispatch::DebugExceptions currently have to monkey patch it to get
the much needed exception for their calculation.

With DebugExceptions.register_interceptor, plugin authors can hook into
DebugExceptions and process the exception, before being rendered. They
can store it into the request and process it on the way back of the
middleware chain execution or act on it straight in the interceptor.

The interceptors can be play blocks, procs, lambdas or any object that
responds to `#call`.
This commit is contained in:
Genadi Samokovarov 2016-02-24 23:19:57 +02:00
parent 1694b02909
commit d25fba89d4
3 changed files with 59 additions and 1 deletions

View file

@ -1,3 +1,15 @@
* Introduce ActionDispatch::DebugExceptions.register_interceptor
Exception aware plugin authors can use the newly introduced
`.register_interceptor` method to get the processed exception, instead of
monkey patching DebugExceptions.
ActionDispatch::DebugExceptions.register_interceptor do |request, exception|
HypoteticalPlugin.capture_exception(request, exception)
end
*Genadi Samokovarov*
* Output only one Content-Security-Policy nonce header value per request.
Fixes #32597.

View file

@ -50,10 +50,18 @@ module ActionDispatch
end
end
def initialize(app, routes_app = nil, response_format = :default)
cattr_reader :interceptors, instance_accessor: false, default: []
def self.register_interceptor(object = nil, &block)
interceptor = object || block
interceptors << interceptor
end
def initialize(app, routes_app = nil, response_format = :default, interceptors = self.class.interceptors)
@app = app
@routes_app = routes_app
@response_format = response_format
@interceptors = interceptors
end
def call(env)
@ -67,12 +75,26 @@ module ActionDispatch
response
rescue Exception => exception
invoke_interceptors(request, exception)
raise exception unless request.show_exceptions?
render_exception(request, exception)
end
private
def invoke_interceptors(request, exception)
backtrace_cleaner = request.get_header("action_dispatch.backtrace_cleaner")
wrapper = ExceptionWrapper.new(backtrace_cleaner, exception)
@interceptors.each do |interceptor|
begin
interceptor.call(request, exception)
rescue Exception
log_error(request, wrapper)
end
end
end
def render_exception(request, exception)
backtrace_cleaner = request.get_header("action_dispatch.backtrace_cleaner")
wrapper = ExceptionWrapper.new(backtrace_cleaner, exception)

View file

@ -3,6 +3,8 @@
require "abstract_unit"
class DebugExceptionsTest < ActionDispatch::IntegrationTest
InterceptedErrorInstance = StandardError.new
class Boomer
attr_accessor :closed
@ -36,6 +38,8 @@ class DebugExceptionsTest < ActionDispatch::IntegrationTest
raise RuntimeError
when %r{/method_not_allowed}
raise ActionController::MethodNotAllowed
when %r{/intercepted_error}
raise InterceptedErrorInstance
when %r{/unknown_http_method}
raise ActionController::UnknownHttpMethod
when %r{/not_implemented}
@ -76,9 +80,13 @@ class DebugExceptionsTest < ActionDispatch::IntegrationTest
end
end
Interceptor = proc { |request, exception| request.set_header("int", exception) }
BadInterceptor = proc { |request, exception| raise "bad" }
RoutesApp = Struct.new(:routes).new(SharedTestRoutes)
ProductionApp = ActionDispatch::DebugExceptions.new(Boomer.new(false), RoutesApp)
DevelopmentApp = ActionDispatch::DebugExceptions.new(Boomer.new(true), RoutesApp)
InterceptedApp = ActionDispatch::DebugExceptions.new(Boomer.new(true), RoutesApp, :default, [Interceptor])
BadInterceptedApp = ActionDispatch::DebugExceptions.new(Boomer.new(true), RoutesApp, :default, [BadInterceptor])
test "skip diagnosis if not showing detailed exceptions" do
@app = ProductionApp
@ -499,4 +507,20 @@ class DebugExceptionsTest < ActionDispatch::IntegrationTest
end
end
end
test "invoke interceptors before rendering" do
@app = InterceptedApp
get "/intercepted_error", headers: { "action_dispatch.show_exceptions" => true }
assert_equal InterceptedErrorInstance, request.get_header("int")
end
test "bad interceptors doesn't debug exceptions" do
@app = BadInterceptedApp
get "/puke", headers: { "action_dispatch.show_exceptions" => true }
assert_response 500
assert_match(/puke/, body)
end
end