mirror of
https://github.com/github/rails.git
synced 2026-04-26 03:00:59 -04:00
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
This commit is contained in:
@@ -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*
|
||||
|
||||
@@ -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?
|
||||
|
||||
@@ -51,6 +51,8 @@ module ActionDispatch
|
||||
autoload :BestStandardsSupport
|
||||
autoload :Callbacks
|
||||
autoload :Cookies
|
||||
autoload :DebugExceptions
|
||||
autoload :ExceptionWrapper
|
||||
autoload :Flash
|
||||
autoload :Head
|
||||
autoload :ParamsParser
|
||||
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
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
|
||||
|
||||
# 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
|
||||
def render_exception(env, exception)
|
||||
wrapper = ExceptionWrapper.new(env, exception)
|
||||
|
||||
# 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"
|
||||
status = wrapper.status_code
|
||||
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 clean_backtrace(env, exception, *args)
|
||||
env['action_dispatch.backtrace_cleaner'] ?
|
||||
env['action_dispatch.backtrace_cleaner'].clean(exception.backtrace, *args) :
|
||||
exception.backtrace
|
||||
end
|
||||
|
||||
def logger(env)
|
||||
env['action_dispatch.logger'] || stderr_logger
|
||||
end
|
||||
|
||||
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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -3,6 +3,7 @@ require 'abstract_unit'
|
||||
module ShowExceptions
|
||||
class ShowExceptionsController < ActionController::Base
|
||||
use ActionDispatch::ShowExceptions
|
||||
use ActionDispatch::DebugExceptions
|
||||
|
||||
def boom
|
||||
raise 'boom!'
|
||||
|
||||
116
actionpack/test/dispatch/debug_exceptions_test.rb
Normal file
116
actionpack/test/dispatch/debug_exceptions_test.rb
Normal file
@@ -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(""foo"=>"[FILTERED]"", 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
|
||||
@@ -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 "rescue with error page when show_exceptions is false" do
|
||||
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" 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
|
||||
test "sets the HTTP charset parameter" do
|
||||
@app = ProductionApp
|
||||
|
||||
get "/", {"foo"=>"bar"}, {'action_dispatch.show_exceptions' => true,
|
||||
'action_dispatch.parameter_filter' => [:foo]}
|
||||
assert_response 500
|
||||
assert_match(""foo"=>"[FILTERED]"", body)
|
||||
get "/", {}, {'action_dispatch.show_exceptions' => true}
|
||||
assert_equal "text/html; charset=utf-8", response.headers["Content-Type"]
|
||||
end
|
||||
|
||||
test "show registered original exception for wrapped exceptions when show_exceptions is false" do
|
||||
test "show registered original exception for wrapped exceptions" 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
|
||||
|
||||
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 = 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)
|
||||
end
|
||||
end
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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*
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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!
|
||||
|
||||
Reference in New Issue
Block a user