diff options
Diffstat (limited to 'actionpack/lib/action_controller/metal/live.rb')
-rw-r--r-- | actionpack/lib/action_controller/metal/live.rb | 60 |
1 files changed, 52 insertions, 8 deletions
diff --git a/actionpack/lib/action_controller/metal/live.rb b/actionpack/lib/action_controller/metal/live.rb index 4c0554d27b..7590fb6843 100644 --- a/actionpack/lib/action_controller/metal/live.rb +++ b/actionpack/lib/action_controller/metal/live.rb @@ -102,17 +102,30 @@ module ActionController end end - message = json.gsub("\n", "\ndata: ") + message = json.gsub(/\n/, "\ndata: ") @stream.write "data: #{message}\n\n" 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,12 +169,26 @@ module ActionController end end - def await_close + # 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 - @cv.wait_until { @closed } + @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 on_error(&block) @error_callback = block end @@ -157,7 +199,7 @@ module ActionController end class Response < ActionDispatch::Response #:nodoc: all - class Header < DelegateClass(Hash) + class Header < DelegateClass(Hash) # :nodoc: def initialize(response, header) @response = response super(header) @@ -255,10 +297,12 @@ module ActionController logger = ActionController::Base.logger return unless logger - message = "\n#{exception.class} (#{exception.message}):\n" - message << exception.annoted_source_code.to_s if exception.respond_to?(:annoted_source_code) - message << " " << exception.backtrace.join("\n ") - logger.fatal("#{message}\n\n") + logger.fatal do + message = "\n#{exception.class} (#{exception.message}):\n" + message << exception.annoted_source_code.to_s if exception.respond_to?(:annoted_source_code) + message << " " << exception.backtrace.join("\n ") + "#{message}\n\n" + end end def response_body=(body) |