From cf122e941e10c2812b7ba5e75c053a28950ddcb6 Mon Sep 17 00:00:00 2001 From: Eric Wong Date: Thu, 3 Jun 2010 15:23:01 -0700 Subject: add Rainbows::Sendfile middleware 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. --- lib/rainbows/sendfile.rb | 67 ++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 67 insertions(+) create mode 100644 lib/rainbows/sendfile.rb (limited to 'lib/rainbows/sendfile.rb') 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 -- cgit v1.2.3-24-ge0c7