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