diff options
Diffstat (limited to 'actionpack/lib/action_dispatch')
20 files changed, 370 insertions, 243 deletions
diff --git a/actionpack/lib/action_dispatch/http/cache.rb b/actionpack/lib/action_dispatch/http/cache.rb index aaed0d750f..bea62b94d2 100644 --- a/actionpack/lib/action_dispatch/http/cache.rb +++ b/actionpack/lib/action_dispatch/http/cache.rb @@ -4,14 +4,18 @@ module ActionDispatch module Http module Cache module Request + + HTTP_IF_MODIFIED_SINCE = 'HTTP_IF_MODIFIED_SINCE'.freeze + HTTP_IF_NONE_MATCH = 'HTTP_IF_NONE_MATCH'.freeze + def if_modified_since - if since = env['HTTP_IF_MODIFIED_SINCE'] + if since = env[HTTP_IF_MODIFIED_SINCE] Time.rfc2822(since) rescue nil end end def if_none_match - env['HTTP_IF_NONE_MATCH'] + env[HTTP_IF_NONE_MATCH] end def not_modified?(modified_at) @@ -43,31 +47,35 @@ module ActionDispatch alias :etag? :etag def last_modified - if last = headers['Last-Modified'] + if last = headers[LAST_MODIFIED] Time.httpdate(last) end end def last_modified? - headers.include?('Last-Modified') + headers.include?(LAST_MODIFIED) end def last_modified=(utc_time) - headers['Last-Modified'] = utc_time.httpdate + headers[LAST_MODIFIED] = utc_time.httpdate end def etag=(etag) key = ActiveSupport::Cache.expand_cache_key(etag) - @etag = self["ETag"] = %("#{Digest::MD5.hexdigest(key)}") + @etag = self[ETAG] = %("#{Digest::MD5.hexdigest(key)}") end private + LAST_MODIFIED = "Last-Modified".freeze + ETAG = "ETag".freeze + CACHE_CONTROL = "Cache-Control".freeze + def prepare_cache_control! @cache_control = {} - @etag = self["ETag"] + @etag = self[ETAG] - if cache_control = self["Cache-Control"] + if cache_control = self[CACHE_CONTROL] cache_control.split(/,\s*/).each do |segment| first, last = segment.split("=") @cache_control[first.to_sym] = last || true @@ -81,28 +89,32 @@ module ActionDispatch end end - DEFAULT_CACHE_CONTROL = "max-age=0, private, must-revalidate" + DEFAULT_CACHE_CONTROL = "max-age=0, private, must-revalidate".freeze + NO_CACHE = "no-cache".freeze + PUBLIC = "public".freeze + PRIVATE = "private".freeze + MUST_REVALIDATE = "must-revalidate".freeze def set_conditional_cache_control! - return if self["Cache-Control"].present? + return if self[CACHE_CONTROL].present? control = @cache_control if control.empty? - headers["Cache-Control"] = DEFAULT_CACHE_CONTROL + headers[CACHE_CONTROL] = DEFAULT_CACHE_CONTROL elsif control[:no_cache] - headers["Cache-Control"] = "no-cache" + headers[CACHE_CONTROL] = NO_CACHE else extras = control[:extras] max_age = control[:max_age] options = [] options << "max-age=#{max_age.to_i}" if max_age - options << (control[:public] ? "public" : "private") - options << "must-revalidate" if control[:must_revalidate] + options << (control[:public] ? PUBLIC : PRIVATE) + options << MUST_REVALIDATE if control[:must_revalidate] options.concat(extras) if extras - headers["Cache-Control"] = options.join(", ") + headers[CACHE_CONTROL] = options.join(", ") end end end diff --git a/actionpack/lib/action_dispatch/http/parameter_filter.rb b/actionpack/lib/action_dispatch/http/parameter_filter.rb index 1480e8f77c..490b46c990 100644 --- a/actionpack/lib/action_dispatch/http/parameter_filter.rb +++ b/actionpack/lib/action_dispatch/http/parameter_filter.rb @@ -20,6 +20,8 @@ module ActionDispatch @filters.present? end + FILTERED = '[FILTERED]'.freeze + def compiled_filter @compiled_filter ||= begin regexps, blocks = compile_filter @@ -29,7 +31,7 @@ module ActionDispatch original_params.each do |key, value| if regexps.find { |r| key =~ r } - value = '[FILTERED]' + value = FILTERED elsif value.is_a?(Hash) value = filter(value) elsif value.is_a?(Array) diff --git a/actionpack/lib/action_dispatch/http/request.rb b/actionpack/lib/action_dispatch/http/request.rb index 69ca050d0c..c5c48ec489 100644 --- a/actionpack/lib/action_dispatch/http/request.rb +++ b/actionpack/lib/action_dispatch/http/request.rb @@ -35,14 +35,6 @@ module ActionDispatch METHOD end - def self.new(env) - if request = env["action_dispatch.request"] && request.instance_of?(self) - return request - end - - super - end - def key?(key) @env.key?(key) end @@ -94,31 +86,31 @@ module ActionDispatch end # Is this a GET (or HEAD) request? - # Equivalent to <tt>request.request_method == :get</tt>. + # Equivalent to <tt>request.request_method_symbol == :get</tt>. def get? HTTP_METHOD_LOOKUP[request_method] == :get end # Is this a POST request? - # Equivalent to <tt>request.request_method == :post</tt>. + # Equivalent to <tt>request.request_method_symbol == :post</tt>. def post? HTTP_METHOD_LOOKUP[request_method] == :post end # Is this a PUT request? - # Equivalent to <tt>request.request_method == :put</tt>. + # Equivalent to <tt>request.request_method_symbol == :put</tt>. def put? HTTP_METHOD_LOOKUP[request_method] == :put end # Is this a DELETE request? - # Equivalent to <tt>request.request_method == :delete</tt>. + # Equivalent to <tt>request.request_method_symbol == :delete</tt>. def delete? HTTP_METHOD_LOOKUP[request_method] == :delete end # Is this a HEAD request? - # Equivalent to <tt>request.method == :head</tt>. + # Equivalent to <tt>request.method_symbol == :head</tt>. def head? HTTP_METHOD_LOOKUP[method] == :head end diff --git a/actionpack/lib/action_dispatch/http/response.rb b/actionpack/lib/action_dispatch/http/response.rb index f1e85559a3..5797c63924 100644 --- a/actionpack/lib/action_dispatch/http/response.rb +++ b/actionpack/lib/action_dispatch/http/response.rb @@ -53,8 +53,10 @@ module ActionDispatch # :nodoc: # information. attr_accessor :charset, :content_type - CONTENT_TYPE = "Content-Type" - + CONTENT_TYPE = "Content-Type".freeze + SET_COOKIE = "Set-Cookie".freeze + LOCATION = "Location".freeze + cattr_accessor(:default_charset) { "utf-8" } include Rack::Response::Helpers @@ -66,10 +68,10 @@ module ActionDispatch # :nodoc: @sending_file = false @blank = false - if content_type = self["Content-Type"] + if content_type = self[CONTENT_TYPE] type, charset = content_type.split(/;\s*charset=/) @content_type = Mime::Type.lookup(type) - @charset = charset || "UTF-8" + @charset = charset || self.class.default_charset end prepare_cache_control! @@ -109,9 +111,9 @@ module ActionDispatch # :nodoc: end def body - str = '' - each { |part| str << part.to_s } - str + strings = [] + each { |part| strings << part.to_s } + strings.join end EMPTY = " " @@ -142,12 +144,12 @@ module ActionDispatch # :nodoc: end def location - headers['Location'] + headers[LOCATION] end alias_method :redirect_url, :location def location=(url) - headers['Location'] = url + headers[LOCATION] = url end def close @@ -158,10 +160,10 @@ module ActionDispatch # :nodoc: assign_default_content_type_and_charset! handle_conditional_get! - @header["Set-Cookie"] = @header["Set-Cookie"].join("\n") if @header["Set-Cookie"].respond_to?(:join) + @header[SET_COOKIE] = @header[SET_COOKIE].join("\n") if @header[SET_COOKIE].respond_to?(:join) if [204, 304].include?(@status) - @header.delete "Content-Type" + @header.delete CONTENT_TYPE [@status, @header, []] else [@status, @header, self] @@ -175,7 +177,7 @@ module ActionDispatch # :nodoc: # assert_equal 'AuthorOfNewPage', r.cookies['author'] def cookies cookies = {} - if header = self["Set-Cookie"] + if header = self[SET_COOKIE] header = header.split("\n") if header.respond_to?(:to_str) header.each do |cookie| if pair = cookie.split(';').first diff --git a/actionpack/lib/action_dispatch/http/url.rb b/actionpack/lib/action_dispatch/http/url.rb index 129a8b1031..64459836b5 100644 --- a/actionpack/lib/action_dispatch/http/url.rb +++ b/actionpack/lib/action_dispatch/http/url.rb @@ -70,7 +70,7 @@ module ActionDispatch host = "" unless options[:subdomain] == false - host << (options[:subdomain] || extract_subdomain(options[:host], tld_length)) + host << (options[:subdomain] || extract_subdomain(options[:host], tld_length)).to_param host << "." end host << (options[:domain] || extract_domain(options[:host], tld_length)) diff --git a/actionpack/lib/action_dispatch/middleware/closed_error.rb b/actionpack/lib/action_dispatch/middleware/closed_error.rb deleted file mode 100644 index 0a4db47f4b..0000000000 --- a/actionpack/lib/action_dispatch/middleware/closed_error.rb +++ /dev/null @@ -1,7 +0,0 @@ -module ActionDispatch - class ClosedError < StandardError #:nodoc: - def initialize(kind) - super "Cannot modify #{kind} because it was closed. This means it was already streamed back to the client or converted to HTTP headers." - end - end -end diff --git a/actionpack/lib/action_dispatch/middleware/cookies.rb b/actionpack/lib/action_dispatch/middleware/cookies.rb index 51cec41a34..39ff58a447 100644 --- a/actionpack/lib/action_dispatch/middleware/cookies.rb +++ b/actionpack/lib/action_dispatch/middleware/cookies.rb @@ -121,10 +121,6 @@ module ActionDispatch @cookies = {} end - attr_reader :closed - alias :closed? :closed - def close!; @closed = true end - def each(&block) @cookies.each(&block) end @@ -165,7 +161,6 @@ module ActionDispatch # Sets the cookie named +name+. The second argument may be the very cookie # value, or a hash of options as documented above. def []=(key, options) - raise ClosedError, :cookies if closed? if options.is_a?(Hash) options.symbolize_keys! value = options[:value] @@ -259,7 +254,6 @@ module ActionDispatch end def []=(key, options) - raise ClosedError, :cookies if closed? if options.is_a?(Hash) options.symbolize_keys! else @@ -298,7 +292,6 @@ module ActionDispatch end def []=(key, options) - raise ClosedError, :cookies if closed? if options.is_a?(Hash) options.symbolize_keys! options[:value] = @verifier.generate(options[:value]) @@ -352,9 +345,6 @@ module ActionDispatch end [status, headers, body] - ensure - cookie_jar = ActionDispatch::Request.new(env).cookie_jar unless cookie_jar - cookie_jar.close! end end end 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..dabbabb1e6 --- /dev/null +++ b/actionpack/lib/action_dispatch/middleware/debug_exceptions.rb @@ -0,0 +1,82 @@ +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' + body = response[2] + body.close if body.respond_to?(:close) + 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 + + exception ? render_exception(env, exception) : response + 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 + + trace = wrapper.application_trace + trace = wrapper.framework_trace if trace.empty? + + 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 << " " << 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/flash.rb b/actionpack/lib/action_dispatch/middleware/flash.rb index e59404ef68..bc5b163931 100644 --- a/actionpack/lib/action_dispatch/middleware/flash.rb +++ b/actionpack/lib/action_dispatch/middleware/flash.rb @@ -93,7 +93,6 @@ module ActionDispatch end def []=(k, v) #:nodoc: - raise ClosedError, :flash if closed? keep(k) @flashes[k] = v end @@ -159,10 +158,6 @@ module ActionDispatch @now ||= FlashNow.new(self) end - attr_reader :closed - alias :closed? :closed - def close!; @closed = true; end - # Keeps either the entire current flash or a specific flash entry available for the next action: # # flash.keep # keeps the entire flash @@ -258,7 +253,6 @@ module ActionDispatch end env[KEY] = new_hash - new_hash.close! end if session.key?('flash') && session['flash'].empty? diff --git a/actionpack/lib/action_dispatch/middleware/public_exceptions.rb b/actionpack/lib/action_dispatch/middleware/public_exceptions.rb new file mode 100644 index 0000000000..85b8d178bf --- /dev/null +++ b/actionpack/lib/action_dispatch/middleware/public_exceptions.rb @@ -0,0 +1,30 @@ +module ActionDispatch + # A simple Rack application that renders exceptions in the given public path. + class PublicExceptions + attr_accessor :public_path + + def initialize(public_path) + @public_path = public_path + end + + def call(env) + status = env["PATH_INFO"][1..-1] + 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 + [404, { "X-Cascade" => "pass" }, []] + end + end + + private + + def render(status, body) + [status, {'Content-Type' => "text/html; charset=#{Response.default_charset}", 'Content-Length' => body.bytesize.to_s}, [body]] + end + end +end
\ No newline at end of file diff --git a/actionpack/lib/action_dispatch/middleware/reloader.rb b/actionpack/lib/action_dispatch/middleware/reloader.rb index 29289a76b4..4f48f1c974 100644 --- a/actionpack/lib/action_dispatch/middleware/reloader.rb +++ b/actionpack/lib/action_dispatch/middleware/reloader.rb @@ -43,34 +43,58 @@ module ActionDispatch # Execute all prepare callbacks. def self.prepare! - new(nil).run_callbacks :prepare + new(nil).prepare! end # Execute all cleanup callbacks. def self.cleanup! - new(nil).run_callbacks :cleanup + new(nil).cleanup! end - def initialize(app) + def initialize(app, condition=nil) @app = app - end - - module CleanupOnClose - def close - super if defined?(super) - ensure - ActionDispatch::Reloader.cleanup! - end + @condition = condition || lambda { true } + @validated = true end def call(env) - run_callbacks :prepare + @validated = @condition.call + prepare! response = @app.call(env) - response[2].extend(CleanupOnClose) + response[2].extend(module_hook) response rescue Exception - run_callbacks :cleanup + cleanup! raise end + + def prepare! #:nodoc: + run_callbacks :prepare if validated? + end + + def cleanup! #:nodoc: + run_callbacks :cleanup if validated? + ensure + @validated = true + end + + private + + def validated? #:nodoc: + @validated + end + + def module_hook #:nodoc: + middleware = self + Module.new do + define_method :close do + begin + super() if defined?(super) + ensure + middleware.cleanup! + end + end + end + end end end diff --git a/actionpack/lib/action_dispatch/middleware/session/abstract_store.rb b/actionpack/lib/action_dispatch/middleware/session/abstract_store.rb index 6bcf099d2c..f75b4d4d04 100644 --- a/actionpack/lib/action_dispatch/middleware/session/abstract_store.rb +++ b/actionpack/lib/action_dispatch/middleware/session/abstract_store.rb @@ -74,10 +74,6 @@ module ActionDispatch class AbstractStore < Rack::Session::Abstract::ID include Compatibility include StaleSessionCheck - - def destroy_session(env, sid, options) - raise '#destroy_session needs to be implemented.' - end end end end diff --git a/actionpack/lib/action_dispatch/middleware/show_exceptions.rb b/actionpack/lib/action_dispatch/middleware/show_exceptions.rb index 8dc2820d37..5eceeece1f 100644 --- a/actionpack/lib/action_dispatch/middleware/show_exceptions.rb +++ b/actionpack/lib/action_dispatch/middleware/show_exceptions.rb @@ -1,176 +1,87 @@ -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 calls an exceptions app that will wrap it in a format for the end user. + # + # The exceptions app should be passed as parameter on initialization + # of ShowExceptions. Everytime there is an exception, ShowExceptions will + # store the exception in env["action_dispatch.exception"], rewrite the + # PATH_INFO to the exception status code and call the rack app. + # + # If the application returns a "X-Cascade" pass response, this middleware + # will send an empty response as result with the correct status code. + # If any exception happens inside the exceptions app, this middleware + # catches the exceptions and returns a FAILSAFE_RESPONSE. 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>"]] - 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 - end - - 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 - rescue Exception => exception - raise exception if env['action_dispatch.show_exceptions'] == false - end - - exception ? render_exception(env, exception) : [status, headers, body] - 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(exception), - :framework_trace => framework_trace(exception), - :full_trace => full_trace(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]) + 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 render(status, body) - [status, {'Content-Type' => "text/html; charset=#{Response.default_charset}", 'Content-Length' => body.bytesize.to_s}, [body]] + 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 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(exception).join("\n ") - logger(env).fatal("#{message}\n\n") - end + def initialize(app, exceptions_app = nil) + if [true, false].include?(exceptions_app) + ActiveSupport::Deprecation.warn "Passing consider_all_requests_local option to ActionDispatch::ShowExceptions middleware no longer works" + exceptions_app = nil end - def application_trace(exception) - clean_backtrace(exception, :silent) + if exceptions_app.nil? + raise ArgumentError, "You need to pass an exceptions_app when initializing ActionDispatch::ShowExceptions. " \ + "In case you want to render pages from a public path, you can use ActionDispatch::PublicExceptions.new('path/to/public')" end - def framework_trace(exception) - clean_backtrace(exception, :noise) - end + @app = app + @exceptions_app = exceptions_app + end - def full_trace(exception) - clean_backtrace(exception, :all) + def call(env) + begin + response = @app.call(env) + rescue Exception => exception + raise exception if env['action_dispatch.show_exceptions'] == false end - def clean_backtrace(exception, *args) - defined?(Rails) && Rails.respond_to?(:backtrace_cleaner) ? - Rails.backtrace_cleaner.clean(exception.backtrace, *args) : - exception.backtrace - end + response || render_exception(env, exception) + end - def logger(env) - env['action_dispatch.logger'] || stderr_logger - end + private - def stderr_logger - Logger.new($stderr) - end + # Define this method because some plugins were monkey patching it. + # Remove this after 3.2 is out with the other deprecations in this class. + def status_code(*) + end - def original_exception(exception) - if registered_original_exception?(exception) - exception.original_exception - else - exception - end + def render_exception(env, exception) + wrapper = ExceptionWrapper.new(env, exception) + status = wrapper.status_code + env["action_dispatch.exception"] = wrapper.exception + env["PATH_INFO"] = "/#{status}" + response = @exceptions_app.call(env) + response[1]['X-Cascade'] == 'pass' ? pass_response(status) : response + rescue Exception => failsafe_error + $stderr.puts "Error during failsafe response: #{failsafe_error}\n #{failsafe_error.backtrace * "\n "}" + FAILSAFE_RESPONSE end - def registered_original_exception?(exception) - exception.respond_to?(:original_exception) && @@rescue_responses.has_key?(exception.original_exception.class.name) + def pass_response(status) + [status, {"Content-Type" => "text/html; charset=#{Response.default_charset}", "Content-Length" => "0"}, []] end end end diff --git a/actionpack/lib/action_dispatch/railtie.rb b/actionpack/lib/action_dispatch/railtie.rb index f18ebabf29..a4f4825f92 100644 --- a/actionpack/lib/action_dispatch/railtie.rb +++ b/actionpack/lib/action_dispatch/railtie.rb @@ -9,10 +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::Response.default_charset = app.config.encoding + + 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 diff --git a/actionpack/lib/action_dispatch/routing.rb b/actionpack/lib/action_dispatch/routing.rb index 1dcd83ceb5..fa300b4a16 100644 --- a/actionpack/lib/action_dispatch/routing.rb +++ b/actionpack/lib/action_dispatch/routing.rb @@ -277,7 +277,6 @@ module ActionDispatch # module Routing autoload :Mapper, 'action_dispatch/routing/mapper' - autoload :Route, 'action_dispatch/routing/route' autoload :RouteSet, 'action_dispatch/routing/route_set' autoload :RoutesProxy, 'action_dispatch/routing/routes_proxy' autoload :UrlFor, 'action_dispatch/routing/url_for' diff --git a/actionpack/lib/action_dispatch/routing/mapper.rb b/actionpack/lib/action_dispatch/routing/mapper.rb index 7947e9d393..4d83c6dee1 100644 --- a/actionpack/lib/action_dispatch/routing/mapper.rb +++ b/actionpack/lib/action_dispatch/routing/mapper.rb @@ -1046,8 +1046,8 @@ module ActionDispatch # Takes same options as <tt>Base#match</tt> as well as: # # [:path_names] - # Allows you to change the paths of the seven default actions. - # Paths not specified are not changed. + # Allows you to change the segment component of the +edit+ and +new+ actions. + # Actions not specified are not changed. # # resources :posts, :path_names => { :new => "brand_new" } # @@ -1286,7 +1286,7 @@ module ActionDispatch action = nil end - if !options.fetch(:as) { true } + if !options.fetch(:as, true) options.delete(:as) else options[:as] = name_for_action(options[:as], action) @@ -1472,8 +1472,16 @@ module ActionDispatch [name_prefix, member_name, prefix] end - candidate = name.select(&:present?).join("_").presence - candidate unless as.nil? && @set.routes.find { |r| r.name == candidate } + if candidate = name.select(&:present?).join("_").presence + # If a name was not explicitly given, we check if it is valid + # and return nil in case it isn't. Otherwise, we pass the invalid name + # forward so the underlying router engine treats it and raises an exception. + if as.nil? + candidate unless @set.routes.find { |r| r.name == candidate } || candidate !~ /\A[_a-z]/i + else + candidate + end + end end end diff --git a/actionpack/lib/action_dispatch/routing/polymorphic_routes.rb b/actionpack/lib/action_dispatch/routing/polymorphic_routes.rb index e989a38d8b..013cf93dbc 100644 --- a/actionpack/lib/action_dispatch/routing/polymorphic_routes.rb +++ b/actionpack/lib/action_dispatch/routing/polymorphic_routes.rb @@ -165,7 +165,7 @@ module ActionDispatch if parent.is_a?(Symbol) || parent.is_a?(String) parent else - ActiveModel::Naming.route_key(parent).singularize + ActiveModel::Naming.singular_route_key(parent) end end else @@ -176,9 +176,11 @@ module ActionDispatch if record.is_a?(Symbol) || record.is_a?(String) route << record elsif record - route << ActiveModel::Naming.route_key(record) - route = [route.join("_").singularize] if inflection == :singular - route << "index" if ActiveModel::Naming.uncountable?(record) && inflection == :plural + if inflection == :singular + route << ActiveModel::Naming.singular_route_key(record) + else + route << ActiveModel::Naming.route_key(record) + end else raise ArgumentError, "Nil location provided. Can't build URI." end diff --git a/actionpack/lib/action_dispatch/routing/route_set.rb b/actionpack/lib/action_dispatch/routing/route_set.rb index 2bcde16110..6d6de36a08 100644 --- a/actionpack/lib/action_dispatch/routing/route_set.rb +++ b/actionpack/lib/action_dispatch/routing/route_set.rb @@ -356,7 +356,7 @@ module ActionDispatch conditions = build_conditions(conditions, valid_conditions, path.names.map { |x| x.to_sym }) route = @set.add_route(app, path, conditions, defaults, name) - named_routes[name] = route if name + named_routes[name] = route if name && !named_routes[name] route end @@ -557,7 +557,7 @@ module ActionDispatch path_addition, params = generate(path_options, path_segments || {}) path << path_addition - ActionDispatch::Http::URL.url_for(options.merge({ + ActionDispatch::Http::URL.url_for(options.merge!({ :path => path, :params => params, :user => user, diff --git a/actionpack/lib/action_dispatch/routing/url_for.rb b/actionpack/lib/action_dispatch/routing/url_for.rb index 39ba83fb9a..22e41c9c16 100644 --- a/actionpack/lib/action_dispatch/routing/url_for.rb +++ b/actionpack/lib/action_dispatch/routing/url_for.rb @@ -145,7 +145,7 @@ module ActionDispatch when String options when nil, Hash - _routes.url_for((options || {}).reverse_merge(url_options).symbolize_keys) + _routes.url_for((options || {}).symbolize_keys.reverse_merge!(url_options)) else polymorphic_url(options) end |