Skip to content

Commit 2bc2263

Browse files
committed
Don't buffer things that shouldn't be buffered.
1 parent 6ddf5d8 commit 2bc2263

File tree

3 files changed

+45
-10
lines changed

3 files changed

+45
-10
lines changed

actionpack/lib/action_dispatch/http/response.rb

Lines changed: 19 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -116,7 +116,11 @@ def respond_to?(method, include_private = false)
116116
end
117117

118118
def to_ary
119-
@buf.to_ary
119+
if @str_body
120+
[body]
121+
else
122+
@buf = @buf.to_ary
123+
end
120124
end
121125

122126
def body
@@ -336,7 +340,13 @@ def message
336340
# Returns the content of the response as a string. This contains the contents of
337341
# any calls to `render`.
338342
def body
339-
@stream.body
343+
if @stream.respond_to?(:to_ary)
344+
@stream.to_ary.join
345+
elsif @stream.respond_to?(:body)
346+
@stream.body
347+
else
348+
@stream
349+
end
340350
end
341351

342352
def write(string)
@@ -345,12 +355,16 @@ def write(string)
345355

346356
# Allows you to manually set or override the response body.
347357
def body=(body)
348-
if body.respond_to?(:to_path)
358+
if body.is_a?(String)
359+
@stream = build_buffer(self, [body])
360+
elsif body.respond_to?(:to_path)
349361
@stream = body
350-
else
362+
elsif body.respond_to?(:to_ary)
351363
synchronize do
352-
@stream = build_buffer self, munge_body_object(body)
364+
@stream = build_buffer(self, body)
353365
end
366+
else
367+
@stream = body
354368
end
355369
end
356370

@@ -490,10 +504,6 @@ def build_buffer(response, body)
490504
Buffer.new response, body
491505
end
492506

493-
def munge_body_object(body)
494-
body.respond_to?(:each) ? body : [body]
495-
end
496-
497507
def assign_default_content_type_and_charset!
498508
return if media_type
499509

actionpack/test/dispatch/response_test.rb

Lines changed: 19 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -42,7 +42,12 @@ def test_write_after_close
4242
def test_each_isnt_called_if_str_body_is_written
4343
# Controller writes and reads response body
4444
each_counter = 0
45-
@response.body = Object.new.tap { |o| o.singleton_class.define_method(:each) { |&block| each_counter += 1; block.call "foo" } }
45+
46+
@response.body = Object.new.tap do |object|
47+
object.singleton_class.define_method(:each) { |&block| each_counter += 1; block.call "foo" }
48+
object.singleton_class.define_method(:to_ary) { enum_for(:each).to_a }
49+
end
50+
4651
@response["X-Foo"] = @response.body
4752

4853
assert_equal 1, each_counter, "#each was not called once"
@@ -647,4 +652,17 @@ class ResponseIntegrationTest < ActionDispatch::IntegrationTest
647652
assert_equal("utf-8", @response.charset)
648653
assert_equal("012345678910", @response.body)
649654
end
655+
656+
test "response does not buffer enumerator body" do
657+
# This is an enumerable body, and it should not be buffered:
658+
body = Enumerator.new do |enumerator|
659+
enumerator << "Hello World"
660+
end
661+
662+
# The response created here should not attempt to buffer the body:
663+
response = ActionDispatch::Response.new(200, { "content-type" => "text/plain" }, body)
664+
665+
# The body should be the same enumerator object, i.e. it should be passed through unchanged:
666+
assert_equal body, response.body
667+
end
650668
end

actionview/lib/action_view/renderer/streaming_template_renderer.rb

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,13 @@ def each(&block)
2525
self
2626
end
2727

28+
# Returns the complete body as a string.
29+
def body
30+
buffer = String.new
31+
each { |part| buffer << part }
32+
buffer
33+
end
34+
2835
private
2936
# This is the same logging logic as in ShowExceptions middleware.
3037
def log_error(exception)

0 commit comments

Comments
 (0)