about summary refs log tree commit homepage
path: root/lib/rainbows/dev_fd_response.rb
diff options
context:
space:
mode:
Diffstat (limited to 'lib/rainbows/dev_fd_response.rb')
-rw-r--r--lib/rainbows/dev_fd_response.rb69
1 files changed, 69 insertions, 0 deletions
diff --git a/lib/rainbows/dev_fd_response.rb b/lib/rainbows/dev_fd_response.rb
new file mode 100644
index 0000000..e4e5f0c
--- /dev/null
+++ b/lib/rainbows/dev_fd_response.rb
@@ -0,0 +1,69 @@
+# -*- encoding: binary -*-
+
+module Rainbows
+
+  # Rack response middleware wrapping any IO-like object with an
+  # OS-level file descriptor associated with it.  May also be used to
+  # create responses from integer file descriptors or existing +IO+
+  # objects.  This may be used in conjunction with the #to_path method
+  # on servers that support it to pass arbitrary file descriptors into
+  # the HTTP response without additional open(2) syscalls
+
+  class DevFdResponse < Struct.new(:app, :to_io, :to_path)
+    include Rack::Utils
+
+    # Rack middleware entry point, we'll just pass through responses
+    # unless they respond to +to_io+ or +to_path+
+    def call(env)
+      status, headers, body = response = app.call(env)
+
+      # totally uninteresting to us if there's no body
+      return response if STATUS_WITH_NO_ENTITY_BODY.include?(status)
+
+      io = body.to_io if body.respond_to?(:to_io)
+      io ||= File.open(body.to_path, 'rb') if body.respond_to?(:to_path)
+      return response if io.nil?
+
+      headers = HeaderHash.new(headers)
+      st = io.stat
+      if st.file?
+        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'
+        end
+      else # unlikely, char/block device file, directory, ...
+        return response
+      end
+      resp = dup # be reentrant here
+      resp.to_path = "/dev/fd/#{io.fileno}"
+      resp.to_io = io
+      [ status, headers.to_hash, resp ]
+    end
+
+    # called by the webserver or other middlewares if they can't
+    # handle #to_path
+    def each(&block)
+      to_io.each(&block)
+    end
+
+    # remain Rack::Lint-compatible for people with wonky systems :P
+    unless File.exist?("/dev/fd/0")
+      alias to_path_orig to_path
+      undef_method :to_path
+    end
+
+    # called by the web server after #each
+    def close
+      begin
+        to_io.close if to_io.respond_to?(:close)
+      rescue IOError # could've been IO::new()'ed and closed
+      end
+    end
+
+  end # class
+end