about summary refs log tree commit homepage
diff options
context:
space:
mode:
authorEric Wong <normalperson@yhbt.net>2010-07-19 10:09:56 +0000
committerEric Wong <normalperson@yhbt.net>2010-07-19 17:04:26 -0700
commit1e6d3d19da2b62bfe7f8fd7827dcad3ee3fe9923 (patch)
tree5de875617e5cf3befb7c0ae9e1add9b2ee2f451e
parentcc18035c5105751a3e67a8e449ee0021fd313ea9 (diff)
downloadrainbows-1e6d3d19da2b62bfe7f8fd7827dcad3ee3fe9923.tar.gz
If a response proxying a pipe (or socket) includes a
Content-Length, do not attempt to outsmart the application
and just use the given Content-Length.

This helps avoid exposing applications to weird internals such
as env["rainbows.autochunk"] and X-Rainbows-* response headers.
-rw-r--r--lib/rainbows/dev_fd_response.rb11
-rw-r--r--lib/rainbows/ev_core.rb14
-rw-r--r--lib/rainbows/event_machine.rb10
-rw-r--r--lib/rainbows/rev/client.rb10
-rw-r--r--t/fast-pipe-response.ru2
5 files changed, 22 insertions, 25 deletions
diff --git a/lib/rainbows/dev_fd_response.rb b/lib/rainbows/dev_fd_response.rb
index c04d64f..451cad7 100644
--- a/lib/rainbows/dev_fd_response.rb
+++ b/lib/rainbows/dev_fd_response.rb
@@ -40,11 +40,12 @@ class Rainbows::DevFdResponse < Struct.new(:app)
       headers['Content-Length'] ||= st.size.to_s
       headers.delete('Transfer-Encoding')
     elsif st.pipe? || st.socket? # epoll-able things
-      if env['rainbows.autochunk']
-        headers['Transfer-Encoding'] = 'chunked'
-        headers.delete('Content-Length')
-      else
-        headers['X-Rainbows-Autochunk'] = 'no'
+      unless headers['Content-Length']
+        if env['rainbows.autochunk']
+          headers['Transfer-Encoding'] = 'chunked'
+        else
+          headers['X-Rainbows-Autochunk'] = 'no'
+        end
       end
 
       # we need to make sure our pipe output is Fiber-compatible
diff --git a/lib/rainbows/ev_core.rb b/lib/rainbows/ev_core.rb
index 5ca693b..dbcdeba 100644
--- a/lib/rainbows/ev_core.rb
+++ b/lib/rainbows/ev_core.rb
@@ -6,6 +6,7 @@ module Rainbows
   module EvCore
     include Unicorn
     include Rainbows::Const
+    include Rainbows::Response
     G = Rainbows::G
     NULL_IO = Unicorn::HttpRequest::NULL_IO
 
@@ -33,6 +34,19 @@ module Rainbows
         quit
     end
 
+    # returns whether to enable response chunking for autochunk models
+    def stream_response_headers(status, headers)
+      if headers['Content-Length']
+        rv = false
+      else
+        rv = !!(headers['Transfer-Encoding'] =~ %r{\Achunked\z}i)
+        rv = false if headers.delete('X-Rainbows-Autochunk') == 'no'
+      end
+      headers[CONNECTION] = CLOSE # TODO: allow keep-alive
+      write(response_header(status, headers))
+      rv
+    end
+
     # TeeInput doesn't map too well to this right now...
     def on_read(data)
       case @state
diff --git a/lib/rainbows/event_machine.rb b/lib/rainbows/event_machine.rb
index 625357e..86cb4eb 100644
--- a/lib/rainbows/event_machine.rb
+++ b/lib/rainbows/event_machine.rb
@@ -50,7 +50,6 @@ module Rainbows
 
     class Client < EM::Connection # :nodoc: all
       include Rainbows::EvCore
-      include Rainbows::Response
       G = Rainbows::G
 
       def initialize(io)
@@ -98,14 +97,7 @@ module Rainbows
 
       # used for streaming sockets and pipes
       def stream_response(status, headers, io)
-        if headers
-          do_chunk = !!(headers['Transfer-Encoding'] =~ %r{\Achunked\z}i)
-          do_chunk = false if headers.delete('X-Rainbows-Autochunk') == 'no'
-          headers[CONNECTION] = CLOSE # TODO: allow keep-alive
-          write(response_header(status, headers))
-        else
-          do_chunk = false
-        end
+        do_chunk = stream_response_headers(status, headers) if headers
         mod = do_chunk ? ResponseChunkPipe : ResponsePipe
         EM.watch(io, mod, self).notify_readable = true
       end
diff --git a/lib/rainbows/rev/client.rb b/lib/rainbows/rev/client.rb
index bc3785f..f067d1b 100644
--- a/lib/rainbows/rev/client.rb
+++ b/lib/rainbows/rev/client.rb
@@ -7,7 +7,6 @@ module Rainbows
     class Client < ::Rev::IO
       include Rainbows::ByteSlice
       include Rainbows::EvCore
-      include Rainbows::Response
       G = Rainbows::G
 
       def initialize(io)
@@ -59,14 +58,7 @@ module Rainbows
 
       # used for streaming sockets and pipes
       def stream_response(status, headers, io, body)
-        if headers
-          do_chunk = !!(headers['Transfer-Encoding'] =~ %r{\Achunked\z}i)
-          do_chunk = false if headers.delete('X-Rainbows-Autochunk') == 'no'
-          headers[CONNECTION] = CLOSE # TODO: allow keep-alive
-          write(response_header(status, headers))
-        else
-          do_chunk = false
-        end
+        do_chunk = stream_response_headers(status, headers) if headers
         # we only want to attach to the Rev::Loop belonging to the
         # main thread in Ruby 1.9
         io = DeferredResponse.new(io, self, do_chunk, body)
diff --git a/t/fast-pipe-response.ru b/t/fast-pipe-response.ru
index 01f4d59..77c8d71 100644
--- a/t/fast-pipe-response.ru
+++ b/t/fast-pipe-response.ru
@@ -1,10 +1,8 @@
 # must be run without Rack::Lint since that clobbers to_path
 use Rainbows::DevFdResponse
 run(lambda { |env|
-  env['rainbows.autochunk'] = false
   [ 200,
     {
-      'X-Rainbows-Autochunk' => 'no',
       'Content-Length' => ::File.stat('random_blob').size.to_s,
       'Content-Type' => 'application/octet-stream',
     },