aboutsummaryrefslogtreecommitdiffstats
path: root/actionpack/lib/action_controller
diff options
context:
space:
mode:
Diffstat (limited to 'actionpack/lib/action_controller')
-rw-r--r--actionpack/lib/action_controller/caching.rb67
-rw-r--r--actionpack/lib/action_controller/caching/fragments.rb148
-rw-r--r--actionpack/lib/action_controller/log_subscriber.rb4
-rw-r--r--actionpack/lib/action_controller/metal.rb6
-rw-r--r--actionpack/lib/action_controller/metal/instrumentation.rb4
-rw-r--r--actionpack/lib/action_controller/metal/live.rb66
-rw-r--r--actionpack/lib/action_controller/metal/request_forgery_protection.rb4
-rw-r--r--actionpack/lib/action_controller/metal/strong_parameters.rb44
-rw-r--r--actionpack/lib/action_controller/test_case.rb15
9 files changed, 98 insertions, 260 deletions
diff --git a/actionpack/lib/action_controller/caching.rb b/actionpack/lib/action_controller/caching.rb
index 0b8fa2ea09..a9a8508abc 100644
--- a/actionpack/lib/action_controller/caching.rb
+++ b/actionpack/lib/action_controller/caching.rb
@@ -1,6 +1,3 @@
-require 'fileutils'
-require 'uri'
-
module ActionController
# \Caching is a cheap way of speeding up slow applications by keeping the result of
# calculations, renderings, and database calls around for subsequent requests.
@@ -23,65 +20,25 @@ module ActionController
# config.action_controller.cache_store = :mem_cache_store, Memcached::Rails.new('localhost:11211')
# config.action_controller.cache_store = MyOwnStore.new('parameter')
module Caching
- extend ActiveSupport::Concern
extend ActiveSupport::Autoload
-
- eager_autoload do
- autoload :Fragments
- end
-
- module ConfigMethods
- def cache_store
- config.cache_store
- end
-
- def cache_store=(store)
- config.cache_store = ActiveSupport::Cache.lookup_store(store)
- end
-
- private
- def cache_configured?
- perform_caching && cache_store
- end
- end
-
- include AbstractController::Callbacks
-
- include ConfigMethods
- include Fragments
+ extend ActiveSupport::Concern
included do
- extend ConfigMethods
-
- config_accessor :default_static_extension
- self.default_static_extension ||= '.html'
-
- config_accessor :perform_caching
- self.perform_caching = true if perform_caching.nil?
-
- class_attribute :_view_cache_dependencies
- self._view_cache_dependencies = []
- helper_method :view_cache_dependencies if respond_to?(:helper_method)
+ include AbstractController::Caching
end
- module ClassMethods
- def view_cache_dependency(&dependency)
- self._view_cache_dependencies += [dependency]
- end
- end
+ private
- def view_cache_dependencies
- self.class._view_cache_dependencies.map { |dep| instance_exec(&dep) }.compact
- end
+ def instrument_payload(key)
+ {
+ controller: controller_name,
+ action: action_name,
+ key: key
+ }
+ end
- protected
- # Convenience accessor.
- def cache(key, options = {}, &block)
- if cache_configured?
- cache_store.fetch(ActiveSupport::Cache.expand_cache_key(key, :controller), options, &block)
- else
- yield
- end
+ def instrument_name
+ "action_controller"
end
end
end
diff --git a/actionpack/lib/action_controller/caching/fragments.rb b/actionpack/lib/action_controller/caching/fragments.rb
deleted file mode 100644
index b9ad51a9cf..0000000000
--- a/actionpack/lib/action_controller/caching/fragments.rb
+++ /dev/null
@@ -1,148 +0,0 @@
-module ActionController
- module Caching
- # Fragment caching is used for caching various blocks within
- # views without caching the entire action as a whole. This is
- # useful when certain elements of an action change frequently or
- # depend on complicated state while other parts rarely change or
- # can be shared amongst multiple parties. The caching is done using
- # the +cache+ helper available in the Action View. See
- # ActionView::Helpers::CacheHelper for more information.
- #
- # While it's strongly recommended that you use key-based cache
- # expiration (see links in CacheHelper for more information),
- # it is also possible to manually expire caches. For example:
- #
- # expire_fragment('name_of_cache')
- module Fragments
- extend ActiveSupport::Concern
-
- included do
- if respond_to?(:class_attribute)
- class_attribute :fragment_cache_keys
- else
- mattr_writer :fragment_cache_keys
- end
-
- self.fragment_cache_keys = []
-
- helper_method :fragment_cache_key if respond_to?(:helper_method)
- end
-
- module ClassMethods
- # Allows you to specify controller-wide key prefixes for
- # cache fragments. Pass either a constant +value+, or a block
- # which computes a value each time a cache key is generated.
- #
- # For example, you may want to prefix all fragment cache keys
- # with a global version identifier, so you can easily
- # invalidate all caches.
- #
- # class ApplicationController
- # fragment_cache_key "v1"
- # end
- #
- # When it's time to invalidate all fragments, simply change
- # the string constant. Or, progressively roll out the cache
- # invalidation using a computed value:
- #
- # class ApplicationController
- # fragment_cache_key do
- # @account.id.odd? ? "v1" : "v2"
- # end
- # end
- def fragment_cache_key(value = nil, &key)
- self.fragment_cache_keys += [key || ->{ value }]
- end
- end
-
- # Given a key (as described in +expire_fragment+), returns
- # a key suitable for use in reading, writing, or expiring a
- # cached fragment. All keys begin with <tt>views/</tt>,
- # followed by any controller-wide key prefix values, ending
- # with the specified +key+ value. The key is expanded using
- # ActiveSupport::Cache.expand_cache_key.
- def fragment_cache_key(key)
- head = self.class.fragment_cache_keys.map { |k| instance_exec(&k) }
- tail = key.is_a?(Hash) ? url_for(key).split("://").last : key
- ActiveSupport::Cache.expand_cache_key([*head, *tail], :views)
- end
-
- # Writes +content+ to the location signified by
- # +key+ (see +expire_fragment+ for acceptable formats).
- def write_fragment(key, content, options = nil)
- return content unless cache_configured?
-
- key = fragment_cache_key(key)
- instrument_fragment_cache :write_fragment, key do
- content = content.to_str
- cache_store.write(key, content, options)
- end
- content
- end
-
- # Reads a cached fragment from the location signified by +key+
- # (see +expire_fragment+ for acceptable formats).
- def read_fragment(key, options = nil)
- return unless cache_configured?
-
- key = fragment_cache_key(key)
- instrument_fragment_cache :read_fragment, key do
- result = cache_store.read(key, options)
- result.respond_to?(:html_safe) ? result.html_safe : result
- end
- end
-
- # Check if a cached fragment from the location signified by
- # +key+ exists (see +expire_fragment+ for acceptable formats).
- def fragment_exist?(key, options = nil)
- return unless cache_configured?
- key = fragment_cache_key(key)
-
- instrument_fragment_cache :exist_fragment?, key do
- cache_store.exist?(key, options)
- end
- end
-
- # Removes fragments from the cache.
- #
- # +key+ can take one of three forms:
- #
- # * String - This would normally take the form of a path, like
- # <tt>pages/45/notes</tt>.
- # * Hash - Treated as an implicit call to +url_for+, like
- # <tt>{ controller: 'pages', action: 'notes', id: 45}</tt>
- # * Regexp - Will remove any fragment that matches, so
- # <tt>%r{pages/\d*/notes}</tt> might remove all notes. Make sure you
- # don't use anchors in the regex (<tt>^</tt> or <tt>$</tt>) because
- # the actual filename matched looks like
- # <tt>./cache/filename/path.cache</tt>. Note: Regexp expiration is
- # only supported on caches that can iterate over all keys (unlike
- # memcached).
- #
- # +options+ is passed through to the cache store's +delete+
- # method (or <tt>delete_matched</tt>, for Regexp keys).
- def expire_fragment(key, options = nil)
- return unless cache_configured?
- key = fragment_cache_key(key) unless key.is_a?(Regexp)
-
- instrument_fragment_cache :expire_fragment, key do
- if key.is_a?(Regexp)
- cache_store.delete_matched(key, options)
- else
- cache_store.delete(key, options)
- end
- end
- end
-
- def instrument_fragment_cache(name, key) # :nodoc:
- payload = {
- controller: controller_name,
- action: action_name,
- key: key
- }
-
- ActiveSupport::Notifications.instrument("#{name}.action_controller", payload) { yield }
- end
- end
- end
-end
diff --git a/actionpack/lib/action_controller/log_subscriber.rb b/actionpack/lib/action_controller/log_subscriber.rb
index 4c9f14e409..a0917b4fdb 100644
--- a/actionpack/lib/action_controller/log_subscriber.rb
+++ b/actionpack/lib/action_controller/log_subscriber.rb
@@ -25,7 +25,9 @@ module ActionController
status = ActionDispatch::ExceptionWrapper.status_code_for_exception(exception_class_name)
end
message = "Completed #{status} #{Rack::Utils::HTTP_STATUS_CODES[status]} in #{event.duration.round}ms"
- message << " (#{additions.join(" | ".freeze)})" unless additions.blank?
+ message << " (#{additions.join(" | ".freeze)})" unless additions.empty?
+ message << "\n\n" if defined?(Rails.env) && Rails.env.development?
+
message
end
end
diff --git a/actionpack/lib/action_controller/metal.rb b/actionpack/lib/action_controller/metal.rb
index f6a93a8940..f6e67b02d7 100644
--- a/actionpack/lib/action_controller/metal.rb
+++ b/actionpack/lib/action_controller/metal.rb
@@ -174,10 +174,8 @@ module ActionController
def response_body=(body)
body = [body] unless body.nil? || body.respond_to?(:each)
response.reset_body!
- body.each { |part|
- next if part.empty?
- response.write part
- }
+ return unless body
+ response.body = body
super
end
diff --git a/actionpack/lib/action_controller/metal/instrumentation.rb b/actionpack/lib/action_controller/metal/instrumentation.rb
index 3dbf34eb2a..bf74b39ac4 100644
--- a/actionpack/lib/action_controller/metal/instrumentation.rb
+++ b/actionpack/lib/action_controller/metal/instrumentation.rb
@@ -19,9 +19,9 @@ module ActionController
:controller => self.class.name,
:action => self.action_name,
:params => request.filtered_parameters,
- :format => request.format.try(:ref),
+ :format => request.format.ref,
:method => request.request_method,
- :path => (request.fullpath rescue "unknown")
+ :path => request.fullpath
}
ActiveSupport::Notifications.instrument("start_processing.action_controller", raw_payload.dup)
diff --git a/actionpack/lib/action_controller/metal/live.rb b/actionpack/lib/action_controller/metal/live.rb
index e3c540bf5f..fc20e7a421 100644
--- a/actionpack/lib/action_controller/metal/live.rb
+++ b/actionpack/lib/action_controller/metal/live.rb
@@ -237,39 +237,55 @@ module ActionController
# This processes the action in a child thread. It lets us return the
# response code and headers back up the rack stack, and still process
# the body in parallel with sending data to the client
- Thread.new {
- t2 = Thread.current
- t2.abort_on_exception = true
-
- # Since we're processing the view in a different thread, copy the
- # thread locals from the main thread to the child thread. :'(
- locals.each { |k,v| t2[k] = v }
-
- begin
- super(name)
- rescue => e
- if @_response.committed?
- begin
- @_response.stream.write(ActionView::Base.streaming_completion_on_exception) if request.format == :html
- @_response.stream.call_on_error
- rescue => exception
- log_error(exception)
- ensure
- log_error(e)
- @_response.stream.close
+ new_controller_thread {
+ ActiveSupport::Dependencies.interlock.running do
+ t2 = Thread.current
+
+ # Since we're processing the view in a different thread, copy the
+ # thread locals from the main thread to the child thread. :'(
+ locals.each { |k,v| t2[k] = v }
+
+ begin
+ super(name)
+ rescue => e
+ if @_response.committed?
+ begin
+ @_response.stream.write(ActionView::Base.streaming_completion_on_exception) if request.format == :html
+ @_response.stream.call_on_error
+ rescue => exception
+ log_error(exception)
+ ensure
+ log_error(e)
+ @_response.stream.close
+ end
+ else
+ error = e
end
- else
- error = e
+ ensure
+ @_response.commit!
end
- ensure
- @_response.commit!
end
}
- @_response.await_commit
+ ActiveSupport::Dependencies.interlock.permit_concurrent_loads do
+ @_response.await_commit
+ end
+
raise error if error
end
+ # Spawn a new thread to serve up the controller in. This is to get
+ # around the fact that Rack isn't based around IOs and we need to use
+ # a thread to stream data from the response bodies. Nobody should call
+ # this method except in Rails internals. Seriously!
+ def new_controller_thread # :nodoc:
+ Thread.new {
+ t2 = Thread.current
+ t2.abort_on_exception = true
+ yield
+ }
+ end
+
def log_error(exception)
logger = ActionController::Base.logger
return unless logger
diff --git a/actionpack/lib/action_controller/metal/request_forgery_protection.rb b/actionpack/lib/action_controller/metal/request_forgery_protection.rb
index 91b3403ad5..b2f0b382b9 100644
--- a/actionpack/lib/action_controller/metal/request_forgery_protection.rb
+++ b/actionpack/lib/action_controller/metal/request_forgery_protection.rb
@@ -378,7 +378,9 @@ module ActionController #:nodoc:
end
def xor_byte_strings(s1, s2)
- s1.bytes.zip(s2.bytes).map { |(c1,c2)| c1 ^ c2 }.pack('c*')
+ s2_bytes = s2.bytes
+ s1.each_byte.with_index { |c1, i| s2_bytes[i] ^= c1 }
+ s2_bytes.pack('C*')
end
# The form's authenticity parameter. Override to provide your own.
diff --git a/actionpack/lib/action_controller/metal/strong_parameters.rb b/actionpack/lib/action_controller/metal/strong_parameters.rb
index d3382ef296..25ec3cf5b6 100644
--- a/actionpack/lib/action_controller/metal/strong_parameters.rb
+++ b/actionpack/lib/action_controller/metal/strong_parameters.rb
@@ -109,7 +109,7 @@ module ActionController
cattr_accessor :permit_all_parameters, instance_accessor: false
cattr_accessor :action_on_unpermitted_parameters, instance_accessor: false
- delegate :keys, :key?, :has_key?, :values, :has_value?, :value?, :empty?, :include?, :inspect,
+ delegate :keys, :key?, :has_key?, :values, :has_value?, :value?, :empty?, :include?,
:as_json, to: :@parameters
# By default, never raise an UnpermittedParameters exception if these
@@ -122,16 +122,6 @@ module ActionController
cattr_accessor :always_permitted_parameters
self.always_permitted_parameters = %w( controller action )
- def self.const_missing(const_name)
- return super unless const_name == :NEVER_UNPERMITTED_PARAMS
- ActiveSupport::Deprecation.warn(<<-MSG.squish)
- `ActionController::Parameters::NEVER_UNPERMITTED_PARAMS` has been deprecated.
- Use `ActionController::Parameters.always_permitted_parameters` instead.
- MSG
-
- always_permitted_parameters
- end
-
# Returns a new instance of <tt>ActionController::Parameters</tt>.
# Also, sets the +permitted+ attribute to the default value of
# <tt>ActionController::Parameters.permit_all_parameters</tt>.
@@ -154,17 +144,21 @@ module ActionController
end
# Returns true if another +Parameters+ object contains the same content and
- # permitted flag, or other Hash-like object contains the same content. This
- # override is in place so you can perform a comparison with `Hash`.
- def ==(other_hash)
- if other_hash.respond_to?(:permitted?)
- super
+ # permitted flag.
+ def ==(other)
+ if other.respond_to?(:permitted?)
+ self.permitted? == other.permitted? && self.parameters == other.parameters
+ elsif other.is_a?(Hash)
+ ActiveSupport::Deprecation.warn <<-WARNING.squish
+ Comparing equality between `ActionController::Parameters` and a
+ `Hash` is deprecated and will be removed in Rails 5.1. Please only do
+ comparisons between instances of `ActionController::Parameters`. If
+ you need to compare to a hash, first convert it using
+ `ActionController::Parameters#new`.
+ WARNING
+ @parameters == other.with_indifferent_access
else
- if other_hash.is_a?(Hash)
- @parameters == other_hash.with_indifferent_access
- else
- @parameters == other_hash
- end
+ @parameters == other
end
end
@@ -584,6 +578,10 @@ module ActionController
dup
end
+ def inspect
+ "<#{self.class} #{@parameters}>"
+ end
+
def method_missing(method_sym, *args, &block)
if @parameters.respond_to?(method_sym)
message = <<-DEPRECATE.squish
@@ -603,12 +601,14 @@ module ActionController
end
protected
+ attr_reader :parameters
+
def permitted=(new_permitted)
@permitted = new_permitted
end
def fields_for_style?
- @parameters.all? { |k, v| k =~ /\A-?\d+\z/ && v.is_a?(Hash) }
+ @parameters.all? { |k, v| k =~ /\A-?\d+\z/ && (v.is_a?(Hash) || v.is_a?(Parameters)) }
end
private
diff --git a/actionpack/lib/action_controller/test_case.rb b/actionpack/lib/action_controller/test_case.rb
index 4702f22810..6548ce326b 100644
--- a/actionpack/lib/action_controller/test_case.rb
+++ b/actionpack/lib/action_controller/test_case.rb
@@ -12,6 +12,17 @@ module ActionController
include Testing::Functional
end
+ module Live
+ # Disable controller / rendering threads in tests. User tests can access
+ # the database on the main thread, so they could open a txn, then the
+ # controller thread will open a new connection and try to access data
+ # that's only visible to the main thread's txn. This is the problem in #23483
+ remove_method :new_controller_thread
+ def new_controller_thread # :nodoc:
+ yield
+ end
+ end
+
# ActionController::TestCase will be deprecated and moved to a gem in Rails 5.1.
# Please use ActionDispatch::IntegrationTest going forward.
class TestRequest < ActionDispatch::TestRequest #:nodoc:
@@ -41,7 +52,7 @@ module ActionController
self.session = session
self.session_options = TestSession::DEFAULT_OPTIONS
@custom_param_parsers = {
- Mime[:xml] => lambda { |raw_post| Hash.from_xml(raw_post)['hash'] }
+ xml: lambda { |raw_post| Hash.from_xml(raw_post)['hash'] }
}
end
@@ -94,7 +105,7 @@ module ActionController
when :url_encoded_form
data = non_path_parameters.to_query
else
- @custom_param_parsers[content_mime_type] = ->(_) { non_path_parameters }
+ @custom_param_parsers[content_mime_type.symbol] = ->(_) { non_path_parameters }
data = non_path_parameters.to_query
end
end