coolio: rename deferred_response => response_pipe
[rainbows.git] / lib / rainbows / sendfile.rb
blob28042172c29a2a2b0f3f3dfc85c8aedff8d76a4c
1 # -*- encoding: binary -*-
2 # This middleware handles X-\Sendfile headers generated by applications
3 # or middlewares down the stack.  It should be placed at the top
4 # (outermost layer) of the middleware stack to avoid having its
5 # +to_path+ method clobbered by another middleware.
7 # This converts X-\Sendfile responses to bodies which respond to the
8 # +to_path+ method which allows certain concurrency models to serve
9 # efficiently using sendfile() or similar.  With multithreaded models
10 # under Ruby 1.9, IO.copy_stream will be used.
12 # This middleware is the opposite of Rack::Sendfile as it
13 # reverses the effect of Rack:::Sendfile.  Unlike many Ruby
14 # web servers, some configurations of \Rainbows! are capable of
15 # serving static files efficiently.
17 # === Compatibility (via IO.copy_stream in Ruby 1.9):
18 # * ThreadSpawn
19 # * ThreadPool
20 # * WriterThreadPool
21 # * WriterThreadSpawn
23 # === Compatibility (Ruby 1.8 and 1.9)
24 # * EventMachine
25 # * NeverBlock (using EventMachine)
27 # DO NOT use this middleware if you're proxying to \Rainbows! with a
28 # server that understands X-\Sendfile (e.g. Apache, Lighttpd) natively.
30 # This does NOT understand X-Accel-Redirect headers intended for nginx.
31 # X-Accel-Redirect requires the application to be highly coupled with
32 # the corresponding nginx configuration, and is thus too complicated to
33 # be worth supporting.
35 # Example config.ru:
37 #    use Rainbows::Sendfile
38 #    run lambda { |env|
39 #      path = "#{Dir.pwd}/random_blob"
40 #      [ 200,
41 #        {
42 #          'X-Sendfile' => path,
43 #          'Content-Type' => 'application/octet-stream'
44 #        },
45 #        []
46 #      ]
47 #    }
49 class Rainbows::Sendfile < Struct.new(:app)
51   # Body wrapper, this allows us to fall back gracefully to
52   # +each+ in case a given concurrency model does not optimize
53   # +to_path+ calls.
54   class Body < Struct.new(:to_path) # :nodoc: all
55     CONTENT_LENGTH = 'Content-Length'.freeze
57     def self.new(path, headers)
58       unless headers[CONTENT_LENGTH]
59         stat = File.stat(path)
60         headers[CONTENT_LENGTH] = stat.size.to_s if stat.file?
61       end
62       super(path)
63     end
65     # fallback in case our +to_path+ doesn't get handled for whatever reason
66     def each(&block)
67       buf = ''
68       File.open(to_path) do |fp|
69         yield buf while fp.read(0x4000, buf)
70       end
71     end
72   end
74   # :stopdoc:
75   HH = Rack::Utils::HeaderHash
76   X_SENDFILE = 'X-Sendfile'
77   # :startdoc:
79   def call(env) # :nodoc:
80     status, headers, body = app.call(env)
81     headers = HH.new(headers)
82     if path = headers.delete(X_SENDFILE)
83       body = Body.new(path, headers) unless body.respond_to?(:to_path)
84     end
85     [ status, headers, body ]
86   end
87 end