From 356787fce80ba545e288f464450483d52782871a Mon Sep 17 00:00:00 2001 From: Aaron Patterson Date: Sun, 29 Jul 2012 15:51:21 -0700 Subject: [PATCH] adding a buffered stream to the response object --- actionpack/lib/action_controller/metal.rb | 2 +- .../lib/action_dispatch/http/response.rb | 44 ++++++++++++++++++- actionpack/test/controller/streaming_test.rb | 30 +++++++++++++ actionpack/test/dispatch/response_test.rb | 20 +++++++++ 4 files changed, 93 insertions(+), 3 deletions(-) create mode 100644 actionpack/test/controller/streaming_test.rb diff --git a/actionpack/lib/action_controller/metal.rb b/actionpack/lib/action_controller/metal.rb index 92433ab462..0a0f5393ce 100644 --- a/actionpack/lib/action_controller/metal.rb +++ b/actionpack/lib/action_controller/metal.rb @@ -187,7 +187,7 @@ module ActionController end def performed? - !!response_body + response_body || (response && response.committed?) end def dispatch(name, request) #:nodoc: diff --git a/actionpack/lib/action_dispatch/http/response.rb b/actionpack/lib/action_dispatch/http/response.rb index 5ef1c86967..912463bc0e 100644 --- a/actionpack/lib/action_dispatch/http/response.rb +++ b/actionpack/lib/action_dispatch/http/response.rb @@ -65,6 +65,36 @@ module ActionDispatch # :nodoc: include ActionDispatch::Http::Cache::Response include MonitorMixin + class Buffer # :nodoc: + def initialize(response, buf) + @response = response + @buf = buf + @closed = false + end + + def write(string) + raise IOError, "closed stream" if closed? + + @response.commit! + @buf.push string + end + + def each(&block) + @buf.each(&block) + end + + def close + @response.commit! + @closed = true + end + + def closed? + @closed + end + end + + attr_reader :stream + def initialize(status = 200, header = {}, body = []) super() @@ -76,6 +106,8 @@ module ActionDispatch # :nodoc: @committed = false @content_type = nil @charset = nil + @stream = build_buffer self, @body + if content_type = self[CONTENT_TYPE] type, charset = content_type.split(/;\s*charset=/) @@ -102,7 +134,7 @@ module ActionDispatch # :nodoc: end def committed? - synchronize { @committed } + @committed end def status=(status) @@ -151,7 +183,7 @@ module ActionDispatch # :nodoc: def body=(body) @blank = true if body == EMPTY - @body = body.respond_to?(:each) ? body : [body] + @body = munge_body_object(body) end def body_parts @@ -214,6 +246,14 @@ module ActionDispatch # :nodoc: private + def build_buffer(response, body) + Buffer.new response, body + end + + def munge_body_object(body) + body.respond_to?(:each) ? body : [body] + end + def assign_default_content_type_and_charset! return if headers[CONTENT_TYPE].present? diff --git a/actionpack/test/controller/streaming_test.rb b/actionpack/test/controller/streaming_test.rb new file mode 100644 index 0000000000..50c98f220a --- /dev/null +++ b/actionpack/test/controller/streaming_test.rb @@ -0,0 +1,30 @@ +require 'abstract_unit' + +module ActionController + class StreamingResponseTest < ActionController::TestCase + class TestController < ActionController::Base + def self.controller_path + 'test' + end + + def basic_stream + %w{ hello world }.each do |word| + response.stream.write word + response.stream.write "\n" + end + response.stream.close + end + end + + tests TestController + + def test_write_to_stream + get :basic_stream + assert_equal "hello\nworld\n", @response.body + end + + def test_write_after_close + @response.stream + end + end +end diff --git a/actionpack/test/dispatch/response_test.rb b/actionpack/test/dispatch/response_test.rb index 51948a623c..e2903d4b36 100644 --- a/actionpack/test/dispatch/response_test.rb +++ b/actionpack/test/dispatch/response_test.rb @@ -14,6 +14,26 @@ class ResponseTest < ActiveSupport::TestCase assert t.join(0.5) end + def test_stream_close + @response.stream.close + assert @response.stream.closed? + end + + def test_stream_write + @response.stream.write "foo" + @response.stream.close + assert_equal "foo", @response.body + end + + def test_write_after_close + @response.stream.close + + e = assert_raises(IOError) do + @response.stream.write "omg" + end + assert_equal "closed stream", e.message + end + def test_response_body_encoding body = ["hello".encode('utf-8')] response = ActionDispatch::Response.new 200, {}, body