about summary refs log tree commit homepage
path: root/lib/rainbows/http_response/body.rb
diff options
context:
space:
mode:
authorEric Wong <normalperson@yhbt.net>2010-07-04 22:16:52 +0000
committerEric Wong <normalperson@yhbt.net>2010-07-04 22:34:09 +0000
commit39b178cdebe275cbc8ce19cf269bea7cd15ff4ca (patch)
treeb7628ed278895fcf70ea3206956be586ac9e1ac5 /lib/rainbows/http_response/body.rb
parent75f5aa9a0d6b37a94afbea3121fc2c16e70a2b1d (diff)
downloadrainbows-39b178cdebe275cbc8ce19cf269bea7cd15ff4ca.tar.gz
This hopefully allows the "sendfile" gem to be required
anywhere in the Rainbows!/Unicorn config file, and not
have to be required via RUBYOPT or the '-r' command-line
switch.

We also modularize HttpResponse and avoids singleton methods
in the response path.  This (hopefully) makes it easier for
individual concurrency models to share code and override
individual methods.
Diffstat (limited to 'lib/rainbows/http_response/body.rb')
-rw-r--r--lib/rainbows/http_response/body.rb118
1 files changed, 118 insertions, 0 deletions
diff --git a/lib/rainbows/http_response/body.rb b/lib/rainbows/http_response/body.rb
new file mode 100644
index 0000000..2ce09da
--- /dev/null
+++ b/lib/rainbows/http_response/body.rb
@@ -0,0 +1,118 @@
+# -*- encoding: binary -*-
+# non-portable body response stuff goes here
+#
+# The sendfile 1.0.0 RubyGem includes IO#sendfile and
+# IO#sendfile_nonblock.   Previous versions of "sendfile" didn't have
+# IO#sendfile_nonblock, and IO#sendfile in previous versions could
+# block other threads under 1.8 with large files
+#
+# IO#sendfile currently (June 2010) beats 1.9 IO.copy_stream with
+# non-Linux support and large files on 32-bit.  We still fall back to
+# IO.copy_stream (if available) if we're dealing with DevFdResponse
+# objects, though.
+#
+# Linux-only splice(2) support via the "io_splice" gem will eventually
+# be added for streaming sockets/pipes, too.
+#
+# * write_body_file - regular files (sendfile or pread+write)
+# * write_body_stream - socket/pipes (read+write, splice later)
+# * write_body_each - generic fallback
+#
+# callgraph is as follows:
+#
+#         write_body
+#         `- write_body_each
+#         `- write_body_path
+#            `- write_body_file
+#            `- write_body_stream
+#
+module Rainbows::HttpResponse::Body # :nodoc:
+  ALIASES = {}
+
+  # to_io is not part of the Rack spec, but make an exception here
+  # since we can conserve path lookups and file descriptors.
+  # \Rainbows! will never get here without checking for the existence
+  # of body.to_path first.
+  def body_to_io(body)
+    if body.respond_to?(:to_io)
+      body.to_io
+    else
+      # try to take advantage of Rainbows::DevFdResponse, calling File.open
+      # is a last resort
+      path = body.to_path
+      path =~ %r{\A/dev/fd/(\d+)\z} ? IO.new($1.to_i) : File.open(path, 'rb')
+    end
+  end
+
+  if IO.method_defined?(:sendfile_nonblock)
+    def write_body_file(sock, body)
+      sock.sendfile(body, 0)
+    end
+  end
+
+  if IO.respond_to?(:copy_stream)
+    unless method_defined?(:write_body_file)
+      # try to use sendfile() via IO.copy_stream, otherwise pread()+write()
+      def write_body_file(sock, body)
+        IO.copy_stream(body, sock, nil, 0)
+      end
+    end
+
+    # only used when body is a pipe or socket that can't handle
+    # pread() semantics
+    def write_body_stream(sock, body)
+      IO.copy_stream(body, sock)
+      ensure
+        body.respond_to?(:close) and body.close
+    end
+  else
+    # fall back to body#each, which is a Rack standard
+    ALIASES[:write_body_stream] = :write_body_each
+  end
+
+  if method_defined?(:write_body_file)
+
+    # middlewares/apps may return with a body that responds to +to_path+
+    def write_body_path(sock, body)
+      inp = body_to_io(body)
+      if inp.stat.file?
+        begin
+          write_body_file(sock, inp)
+        ensure
+          inp.close if inp != body
+        end
+      else
+        write_body_stream(sock, inp)
+      end
+      ensure
+        body.respond_to?(:close) && inp != body and body.close
+    end
+  else
+    def write_body_path(sock, body)
+      write_body_stream(sock, body_to_io(body))
+    end
+  end
+
+  if method_defined?(:write_body_path)
+    def write_body(client, body)
+      body.respond_to?(:to_path) ?
+        write_body_path(client, body) :
+        write_body_each(client, body)
+    end
+  else
+    ALIASES[:write_body] = :write_body_each
+  end
+
+  # generic body writer, used for most dynamically generated responses
+  def write_body_each(socket, body)
+    body.each { |chunk| socket.write(chunk) }
+    ensure
+      body.respond_to?(:close) and body.close
+  end
+
+  def self.included(klass)
+    ALIASES.each do |new_method, orig_method|
+      klass.__send__(:alias_method, new_method, orig_method)
+    end
+  end
+end