about summary refs log tree commit homepage
path: root/lib/rainbows/rev/deferred_response.rb
diff options
context:
space:
mode:
authorEric Wong <normalperson@yhbt.net>2009-11-07 12:23:26 -0800
committerEric Wong <normalperson@yhbt.net>2009-11-07 12:30:47 -0800
commit7e35ea595f4742ace9579402323515031d69fc87 (patch)
tree006c5a6a57c159da466afddc5a7edfb086f6b1cf /lib/rainbows/rev/deferred_response.rb
parent1266417999aeb939d4e2a7d01aa6730f13cae9fa (diff)
downloadrainbows-7e35ea595f4742ace9579402323515031d69fc87.tar.gz
This will make things easier to manage with more
Rev-based concurrency models.
Diffstat (limited to 'lib/rainbows/rev/deferred_response.rb')
-rw-r--r--lib/rainbows/rev/deferred_response.rb70
1 files changed, 70 insertions, 0 deletions
diff --git a/lib/rainbows/rev/deferred_response.rb b/lib/rainbows/rev/deferred_response.rb
new file mode 100644
index 0000000..d97abbe
--- /dev/null
+++ b/lib/rainbows/rev/deferred_response.rb
@@ -0,0 +1,70 @@
+# -*- encoding: binary -*-
+module Rainbows
+  module Rev
+
+    # this is class is specific to Rev for writing large static files
+    # or proxying IO-derived objects
+    class DeferredResponse < ::Rev::IO
+      include Unicorn
+      include Rainbows::Const
+      G = Rainbows::G
+      HH = Rack::Utils::HeaderHash
+
+      def self.defer!(client, response, out)
+        body = response.last
+        headers = HH.new(response[1])
+
+        # to_io is not part of the Rack spec, but make an exception
+        # here since we can't get here without checking to_path first
+        io = body.to_io if body.respond_to?(:to_io)
+        io ||= ::IO.new($1.to_i) if body.to_path =~ %r{\A/dev/fd/(\d+)\z}
+        io ||= File.open(body.to_path, 'rb')
+        st = io.stat
+
+        if st.socket? || st.pipe?
+          do_chunk = !!(headers['Transfer-Encoding'] =~ %r{\Achunked\z}i)
+          do_chunk = false if headers.delete('X-Rainbows-Autochunk') == 'no'
+          # too tricky to support keepalive/pipelining when a response can
+          # take an indeterminate amount of time here.
+          if out.nil?
+            do_chunk = false
+          else
+            out[0] = CONN_CLOSE
+          end
+
+          io = new(io, client, do_chunk, body).attach(::Rev::Loop.default)
+        elsif st.file?
+          headers.delete('Transfer-Encoding')
+          headers['Content-Length'] ||= st.size.to_s
+        else # char/block device, directory, whatever... nobody cares
+          return response
+        end
+        client.defer_body(io)
+        [ response.first, headers.to_hash, [] ]
+      end
+
+      def self.write(client, response, out)
+        response.last.respond_to?(:to_path) and
+          response = defer!(client, response, out)
+        HttpResponse.write(client, response, out)
+      end
+
+      def initialize(io, client, do_chunk, body)
+        super(io)
+        @client, @do_chunk, @body = client, do_chunk, body
+      end
+
+      def on_read(data)
+        @do_chunk and @client.write(sprintf("%x\r\n", data.size))
+        @client.write(data)
+        @do_chunk and @client.write("\r\n")
+      end
+
+      def on_close
+        @do_chunk and @client.write("0\r\n\r\n")
+        @client.quit
+        @body.respond_to?(:close) and @body.close
+      end
+    end # class DeferredResponse
+  end # module Rev
+end # module Rainbows