aboutsummaryrefslogtreecommitdiffstats
path: root/actionpack
diff options
context:
space:
mode:
authorAaron Patterson <aaron.patterson@gmail.com>2014-03-12 17:40:08 -0700
committerAaron Patterson <aaron.patterson@gmail.com>2014-03-12 17:40:08 -0700
commit3df07d093a1e4207caa63fd2e3b67599211f5800 (patch)
tree253cf63f6757d8bfe0ba17dabdabc06305fe87a1 /actionpack
parentc0a783610f5cf77050a55ad70b2cd2cf657bffe3 (diff)
downloadrails-3df07d093a1e4207caa63fd2e3b67599211f5800.tar.gz
rails-3df07d093a1e4207caa63fd2e3b67599211f5800.tar.bz2
rails-3df07d093a1e4207caa63fd2e3b67599211f5800.zip
use the body proxy to freeze headers
avoid freezing the headers until the web server has actually read data from the body proxy. Once the webserver has read data, then we should throw an error if someone tries to set a header
Diffstat (limited to 'actionpack')
-rw-r--r--actionpack/lib/action_controller/metal/live.rb10
-rw-r--r--actionpack/lib/action_controller/test_case.rb2
-rw-r--r--actionpack/lib/action_dispatch/http/response.rb37
-rw-r--r--actionpack/test/controller/live_stream_test.rb6
-rw-r--r--actionpack/test/dispatch/live_response_test.rb14
5 files changed, 57 insertions, 12 deletions
diff --git a/actionpack/lib/action_controller/metal/live.rb b/actionpack/lib/action_controller/metal/live.rb
index fe63cf2b98..41b997a755 100644
--- a/actionpack/lib/action_controller/metal/live.rb
+++ b/actionpack/lib/action_controller/metal/live.rb
@@ -125,9 +125,11 @@ module ActionController
end
def each
+ @response.sending!
while str = @buf.pop
yield str
end
+ @response.sent!
end
def close
@@ -179,12 +181,16 @@ module ActionController
private
- def finalize_response
+ def before_committed
super
jar = request.cookie_jar
# The response can be committed multiple times
jar.write self unless committed?
- jar.commit!
+ end
+
+ def before_sending
+ super
+ request.cookie_jar.commit!
headers.freeze
end
diff --git a/actionpack/lib/action_controller/test_case.rb b/actionpack/lib/action_controller/test_case.rb
index 009f83861d..e9166d8747 100644
--- a/actionpack/lib/action_controller/test_case.rb
+++ b/actionpack/lib/action_controller/test_case.rb
@@ -595,7 +595,7 @@ module ActionController
@controller.process(name)
if cookies = @request.env['action_dispatch.cookies']
- unless cookies.committed?
+ unless @response.committed?
cookies.write(@response)
end
end
diff --git a/actionpack/lib/action_dispatch/http/response.rb b/actionpack/lib/action_dispatch/http/response.rb
index b5454f519f..3d27ff2b24 100644
--- a/actionpack/lib/action_dispatch/http/response.rb
+++ b/actionpack/lib/action_dispatch/http/response.rb
@@ -91,7 +91,10 @@ module ActionDispatch # :nodoc:
end
def each(&block)
- @buf.each(&block)
+ @response.sending!
+ x = @buf.each(&block)
+ @response.sent!
+ x
end
def close
@@ -118,6 +121,8 @@ module ActionDispatch # :nodoc:
@blank = false
@cv = new_cond
@committed = false
+ @sending = false
+ @sent = false
@content_type = nil
@charset = nil
@@ -138,18 +143,37 @@ module ActionDispatch # :nodoc:
end
end
+ def await_sent
+ synchronize { @cv.wait_until { @sent } }
+ end
+
def commit!
synchronize do
- finalize_response
+ before_committed
@committed = true
@cv.broadcast
end
end
- def committed?
- @committed
+ def sending!
+ synchronize do
+ before_sending
+ @sending = true
+ @cv.broadcast
+ end
end
+ def sent!
+ synchronize do
+ @sent = true
+ @cv.broadcast
+ end
+ end
+
+ def sending?; synchronize { @sending }; end
+ def committed?; synchronize { @committed }; end
+ def sent?; synchronize { @sent }; end
+
# Sets the HTTP status code.
def status=(status)
@status = Rack::Utils.status_code(status)
@@ -274,7 +298,10 @@ module ActionDispatch # :nodoc:
private
- def finalize_response
+ def before_committed
+ end
+
+ def before_sending
end
def merge_default_headers(original, default)
diff --git a/actionpack/test/controller/live_stream_test.rb b/actionpack/test/controller/live_stream_test.rb
index cc232f6b6e..2fd5c930ba 100644
--- a/actionpack/test/controller/live_stream_test.rb
+++ b/actionpack/test/controller/live_stream_test.rb
@@ -44,7 +44,7 @@ module ActionController
tests SSETestController
def wait_for_response_stream_close
- response.stream.await_close
+ response.body
end
def test_basic_sse
@@ -186,8 +186,8 @@ module ActionController
tests TestController
def assert_stream_closed
- response.stream.await_close
assert response.stream.closed?, 'stream should be closed'
+ assert response.sent?, 'stream should be sent'
end
def capture_log_output
@@ -229,6 +229,7 @@ module ActionController
@controller.response = @response
t = Thread.new(@response) { |resp|
+ resp.await_commit
resp.stream.each do |part|
assert_equal parts.shift, part
ol = @controller.latch
@@ -268,6 +269,7 @@ module ActionController
assert_raises(ActionView::MissingTemplate) do
get :exception_in_view
end
+ assert response.body
assert_stream_closed
end
diff --git a/actionpack/test/dispatch/live_response_test.rb b/actionpack/test/dispatch/live_response_test.rb
index 336fdd569b..512f3a8a7a 100644
--- a/actionpack/test/dispatch/live_response_test.rb
+++ b/actionpack/test/dispatch/live_response_test.rb
@@ -35,6 +35,7 @@ module ActionController
@response.stream.close
}
+ @response.await_commit
@response.each do |part|
assert_equal 'foo', part
latch.release
@@ -59,21 +60,30 @@ module ActionController
assert_nil @response.headers['Content-Length']
end
- def test_headers_cannot_be_written_after_write
+ def test_headers_cannot_be_written_after_webserver_reads
@response.stream.write 'omg'
+ latch = ActiveSupport::Concurrency::Latch.new
+ t = Thread.new {
+ @response.stream.each do |chunk|
+ latch.release
+ end
+ }
+
+ latch.await
assert @response.headers.frozen?
e = assert_raises(ActionDispatch::IllegalStateError) do
@response.headers['Content-Length'] = "zomg"
end
assert_equal 'header already sent', e.message
+ @response.stream.close
+ t.join
end
def test_headers_cannot_be_written_after_close
@response.stream.close
- assert @response.headers.frozen?
e = assert_raises(ActionDispatch::IllegalStateError) do
@response.headers['Content-Length'] = "zomg"
end