about summary refs log tree commit homepage
path: root/lib/rainbows/sendfile.rb
diff options
context:
space:
mode:
authorEric Wong <normalperson@yhbt.net>2010-06-03 15:23:01 -0700
committerEric Wong <normalperson@yhbt.net>2010-06-03 15:26:01 -0700
commitcf122e941e10c2812b7ba5e75c053a28950ddcb6 (patch)
treef0c2ba767c7e7af0384a6c4ff470f4fc4903bad5 /lib/rainbows/sendfile.rb
parent5a0a190253434d3eef5332d979d56f3e16e876bd (diff)
downloadrainbows-cf122e941e10c2812b7ba5e75c053a28950ddcb6.tar.gz
This lets most concurrency models understand and process
X-Sendfile efficiently with IO.copy_stream under Ruby 1.9.
EventMachine can take advantage of this middleware under
both Ruby 1.8 and Ruby 1.9.
Diffstat (limited to 'lib/rainbows/sendfile.rb')
-rw-r--r--lib/rainbows/sendfile.rb67
1 files changed, 67 insertions, 0 deletions
diff --git a/lib/rainbows/sendfile.rb b/lib/rainbows/sendfile.rb
new file mode 100644
index 0000000..1fa832c
--- /dev/null
+++ b/lib/rainbows/sendfile.rb
@@ -0,0 +1,67 @@
+# -*- encoding: binary -*-
+module Rainbows
+
+# Convert X-Sendfile headers into Rack response bodies that respond
+# to the +to_path+ method which allows certain concurrency models to
+# serve efficiently using sendfile() or similar.  With multithreaded
+# models under Ruby 1.9, IO.copy_stream will be used.
+#
+# This middleware is recommended for EventMachine users regardless
+# of Ruby version and 1.9 users with any Thread-based concurrency
+# models.  DO NOT use this middleware if you're proxying \Rainbows!
+# with a server (e.g. Apache, Lighttpd) that understands X-Sendfile
+# natively.
+#
+# This does NOT understand X-Accel-Redirect headers intended for
+# nginx, that is much more complicated to configure and support
+# as it is highly coupled with the corresponding nginx configuration.
+
+class Sendfile < Struct.new(:app)
+
+  # :nodoc:
+  HH = Rack::Utils::HeaderHash
+
+  # Body wrapper, this allows us to fall back gracefully to
+  # #each in case a given concurrency model does not optimize
+  # #to_path calls.
+  class Body < Struct.new(:to_io)
+
+    def initialize(path, headers)
+      # Rainbows! will try #to_io if #to_path exists to avoid unnecessary
+      # open() calls.
+      self.to_io = File.open(path, 'rb')
+
+      unless headers['Content-Length']
+        stat = to_io.stat
+        headers['Content-Length'] = stat.size.to_s if stat.file?
+      end
+    end
+
+    def to_path
+      to_io.path
+    end
+
+    # fallback in case our #to_path doesn't get handled for whatever reason
+    def each(&block)
+      buf = ''
+      while to_io.read(0x4000, buf)
+        yield buf
+      end
+    end
+
+    def close
+      to_io.close
+    end
+  end
+
+  def call(env)
+    status, headers, body = app.call(env)
+    headers = HH.new(headers)
+    if path = headers.delete('X-Sendfile')
+      body = Body.new(path, headers) unless body.respond_to?(:to_path)
+    end
+    [ status, headers, body ]
+  end
+end
+
+end