diff options
Diffstat (limited to 'actionpack/lib/action_controller')
4 files changed, 60 insertions, 8 deletions
diff --git a/actionpack/lib/action_controller/metal/live.rb b/actionpack/lib/action_controller/metal/live.rb index 4c0554d27b..67875141cb 100644 --- a/actionpack/lib/action_controller/metal/live.rb +++ b/actionpack/lib/action_controller/metal/live.rb @@ -107,12 +107,25 @@ module ActionController end end + class ClientDisconnected < RuntimeError + end + class Buffer < ActionDispatch::Response::Buffer #:nodoc: include MonitorMixin + # Ignore that the client has disconnected. + # + # If this value is `true`, calling `write` after the client + # disconnects will result in the written content being silently + # discarded. If this value is `false` (the default), a + # ClientDisconnected exception will be raised. + attr_accessor :ignore_disconnect + def initialize(response) @error_callback = lambda { true } @cv = new_cond + @aborted = false + @ignore_disconnect = false super(response, SizedQueue.new(10)) end @@ -123,6 +136,17 @@ module ActionController end super + + unless connected? + @buf.clear + + unless @ignore_disconnect + # Raise ClientDisconnected, which is a RuntimeError (not an + # IOError), because that's more appropriate for something beyond + # the developer's control. + raise ClientDisconnected, "client disconnected" + end + end end def each @@ -133,6 +157,10 @@ module ActionController @response.sent! end + # Write a 'close' event to the buffer; the producer/writing thread + # uses this to notify us that it's finished supplying content. + # + # See also #abort. def close synchronize do super @@ -141,6 +169,26 @@ module ActionController end end + # Inform the producer/writing thread that the client has + # disconnected; the reading thread is no longer interested in + # anything that's being written. + # + # See also #close. + def abort + synchronize do + @aborted = true + @buf.clear + end + end + + # Is the client still connected and waiting for content? + # + # The result of calling `write` when this is `false` is determined + # by `ignore_disconnect`. + def connected? + !@aborted + end + def await_close synchronize do @cv.wait_until { @closed } diff --git a/actionpack/lib/action_controller/metal/redirecting.rb b/actionpack/lib/action_controller/metal/redirecting.rb index 136e086d0d..3feb737277 100644 --- a/actionpack/lib/action_controller/metal/redirecting.rb +++ b/actionpack/lib/action_controller/metal/redirecting.rb @@ -72,7 +72,7 @@ module ActionController self.status = _extract_redirect_to_status(options, response_status) self.location = _compute_redirect_to_location(options) - self.response_body = "<html><body>You are being <a href=\"#{ERB::Util.h(location)}\">redirected</a>.</body></html>" + self.response_body = "<html><body>You are being <a href=\"#{ERB::Util.unwrapped_html_escape(location)}\">redirected</a>.</body></html>" end def _compute_redirect_to_location(options) #:nodoc: diff --git a/actionpack/lib/action_controller/metal/streaming.rb b/actionpack/lib/action_controller/metal/streaming.rb index 62d5931b45..04401cad7b 100644 --- a/actionpack/lib/action_controller/metal/streaming.rb +++ b/actionpack/lib/action_controller/metal/streaming.rb @@ -183,7 +183,7 @@ module ActionController #:nodoc: # You may also want to configure other parameters like <tt>:tcp_nodelay</tt>. # Please check its documentation for more information: http://unicorn.bogomips.org/Unicorn/Configurator.html#method-i-listen # - # If you are using Unicorn with Nginx, you may need to tweak Nginx. + # If you are using Unicorn with NGINX, you may need to tweak NGINX. # Streaming should work out of the box on Rainbows. # # ==== Passenger diff --git a/actionpack/lib/action_controller/metal/strong_parameters.rb b/actionpack/lib/action_controller/metal/strong_parameters.rb index 0236af4a19..c3c3e4c4f1 100644 --- a/actionpack/lib/action_controller/metal/strong_parameters.rb +++ b/actionpack/lib/action_controller/metal/strong_parameters.rb @@ -129,8 +129,12 @@ module ActionController # Attribute that keeps track of converted arrays, if any, to avoid double # looping in the common use case permit + mass-assignment. Defined in a # method to instantiate it only if needed. + # + # Testing membership still loops, but it's going to be faster than our own + # loop that converts values. Also, we are not going to build a new array + # object per fetch. def converted_arrays - @converted_arrays ||= {} + @converted_arrays ||= Set.new end # Returns +true+ if the parameter is permitted, +false+ otherwise. @@ -333,15 +337,15 @@ module ActionController private def convert_hashes_to_parameters(key, value, assign_if_converted=true) - converted = convert_value_to_parameters(key, value) + converted = convert_value_to_parameters(value) self[key] = converted if assign_if_converted && !converted.equal?(value) converted end - def convert_value_to_parameters(key, value) - if value.is_a?(Array) && !converted_arrays.member?(key) - converted = value.map { |v| convert_value_to_parameters(nil, v) } - converted_arrays[key] = converted if key + def convert_value_to_parameters(value) + if value.is_a?(Array) && !converted_arrays.member?(value) + converted = value.map { |_| convert_value_to_parameters(_) } + converted_arrays << converted converted elsif value.is_a?(Parameters) || !value.is_a?(Hash) value |