about summary refs log tree commit homepage
path: root/lib/rainbows/response/body.rb
diff options
context:
space:
mode:
Diffstat (limited to 'lib/rainbows/response/body.rb')
-rw-r--r--lib/rainbows/response/body.rb118
1 files changed, 118 insertions, 0 deletions
diff --git a/lib/rainbows/response/body.rb b/lib/rainbows/response/body.rb
new file mode 100644
index 0000000..8d8ec27
--- /dev/null
+++ b/lib/rainbows/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::Response::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