Skip to content

Commit

Permalink
Fixes #23964
Browse files Browse the repository at this point in the history
  - Adds #each_chunk to ActionDispatch::Response. it's a method which
    will be called by ActionDispatch::Response#each.
  - Make Response#each a proper method instead of delegating to @stream
  - In Live, instead of overriding #each, override #each_chunk.
  - `#each` should just spit out @str_body if it's already set
  - Adds #test_set_header_after_read_body_during_action
    to prove this fixes #23964
  - Adds #test_each_isnt_called_if_str_body_is_written to
    ensure #each_chunk is not called when @str_body is available
  - Call `@response.sent!` in AC::TestCase's #perform so a test response
    acts a bit more like a real response. Makes test that call  `#assert_stream_closed`
    pass again.
  - Additionally assert `#committed?` in `#assert_stream_closed`
  - Make test that was calling @response.stream.each pass again by
    calling @response.each instead.
  • Loading branch information
rthbound committed Mar 14, 2016
1 parent 7b96d86 commit b43158a
Show file tree
Hide file tree
Showing 6 changed files with 70 additions and 19 deletions.
16 changes: 8 additions & 8 deletions actionpack/lib/action_controller/metal/live.rb
Original file line number Diff line number Diff line change
Expand Up @@ -163,14 +163,6 @@ def write(string)
end
end

def each
@response.sending!
while str = @buf.pop
yield str
end
@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.
#
Expand Down Expand Up @@ -210,6 +202,14 @@ def on_error(&block)
def call_on_error
@error_callback.call
end

private

def each_chunk(&block)
while str = @buf.pop
yield str
end
end
end

class Response < ActionDispatch::Response #:nodoc: all
Expand Down
2 changes: 2 additions & 0 deletions actionpack/lib/action_controller/test_case.rb
Original file line number Diff line number Diff line change
Expand Up @@ -554,6 +554,8 @@ def process(action, *args)
end
@request.query_string = ''

@response.sent!

@response
end

Expand Down
33 changes: 24 additions & 9 deletions actionpack/lib/action_dispatch/http/response.rb
Original file line number Diff line number Diff line change
Expand Up @@ -68,7 +68,13 @@ def to_hash
alias_method :headers, :header

delegate :[], :[]=, :to => :@header
delegate :each, :to => :@stream

def each(&block)
sending!
x = @stream.each(&block)
sent!
x
end

CONTENT_TYPE = "Content-Type".freeze
SET_COOKIE = "Set-Cookie".freeze
Expand Down Expand Up @@ -97,10 +103,10 @@ def initialize(response, buf)

def body
@str_body ||= begin
buf = ''
each { |chunk| buf << chunk }
buf
end
buf = ''
each { |chunk| buf << chunk }
buf
end
end

def write(string)
Expand All @@ -112,10 +118,13 @@ def write(string)
end

def each(&block)
@response.sending!
x = @buf.each(&block)
@response.sent!
x
if @str_body
return enum_for(:each) unless block_given?

yield @str_body
else
each_chunk(&block)
end
end

def abort
Expand All @@ -129,6 +138,12 @@ def close
def closed?
@closed
end

private

def each_chunk(&block)
@buf.each(&block) # extract into own method
end
end

def self.create(status = 200, header = {}, body = [], default_headers: self.default_headers)
Expand Down
3 changes: 2 additions & 1 deletion actionpack/test/controller/live_stream_test.rb
Original file line number Diff line number Diff line change
Expand Up @@ -246,7 +246,8 @@ def ignore_client_disconnect

def assert_stream_closed
assert response.stream.closed?, 'stream should be closed'
assert response.sent?, 'stream should be sent'
assert response.committed?, 'response should be committed'
assert response.sent?, 'response should be sent'
end

def capture_log_output
Expand Down
2 changes: 1 addition & 1 deletion actionpack/test/dispatch/live_response_test.rb
Original file line number Diff line number Diff line change
Expand Up @@ -65,7 +65,7 @@ def test_headers_cannot_be_written_after_webserver_reads
latch = Concurrent::CountDownLatch.new

t = Thread.new {
@response.stream.each do
@response.each do
latch.count_down
end
}
Expand Down
33 changes: 33 additions & 0 deletions actionpack/test/dispatch/response_test.rb
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,39 @@ def test_write_after_close
assert_equal "closed stream", e.message
end

def test_each_isnt_called_if_str_body_is_written
# Controller writes and reads response body
each_counter = 0
@response.body = Object.new.tap {|o| o.singleton_class.send(:define_method, :each) { |&block| each_counter += 1; block.call 'foo' } }
@response['X-Foo'] = @response.body

assert_equal 1, each_counter, "#each was not called once"

# Build response
status, headers, body = @response.to_a

assert_equal 200, status
assert_equal "foo", headers['X-Foo']
assert_equal "foo", body.each.to_a.join

# Show that #each was not called twice
assert_equal 1, each_counter, "#each was not called once"
end

def test_set_header_after_read_body_during_action
@response.body

# set header after the action reads back @response.body
@response['x-header'] = "Best of all possible worlds."

# the response can be built.
status, headers, body = @response.to_a
assert_equal 200, status
assert_equal "", body.body

assert_equal "Best of all possible worlds.", headers['x-header']
end

def test_read_body_during_action
@response.body = "Hello, World!"

Expand Down

0 comments on commit b43158a

Please sign in to comment.