upgrade to Kgio 2.x and Unicorn 3.x
[rainbows.git] / lib / rainbows / dev_fd_response.rb
blob67c94d70b18bccad258a362407eb7466a997293a
1 # -*- encoding: binary -*-
3 # Rack response middleware wrapping any IO-like object with an
4 # OS-level file descriptor associated with it.  May also be used to
5 # create responses from integer file descriptors or existing +IO+
6 # objects.  This may be used in conjunction with the #to_path method
7 # on servers that support it to pass arbitrary file descriptors into
8 # the HTTP response without additional open(2) syscalls
10 # This middleware is currently a no-op for Rubinius, as it lacks
11 # IO.copy_stream in 1.9 and also due to a bug here:
12 #   http://github.com/evanphx/rubinius/issues/379
14 class Rainbows::DevFdResponse < Struct.new(:app)
16   # :stopdoc:
17   FD_MAP = Rainbows::FD_MAP
19   # make this a no-op under Rubinius, it's pointless anyways
20   # since Rubinius doesn't have IO.copy_stream
21   def self.new(app)
22     app
23   end if defined?(RUBY_ENGINE) && RUBY_ENGINE == 'rbx'
24   include Rack::Utils
26   # Rack middleware entry point, we'll just pass through responses
27   # unless they respond to +to_io+ or +to_path+
28   def call(env)
29     status, headers, body = response = app.call(env)
31     # totally uninteresting to us if there's no body
32     return response if STATUS_WITH_NO_ENTITY_BODY.include?(status)
34     io = body.to_io if body.respond_to?(:to_io)
35     io ||= File.open(body.to_path) if body.respond_to?(:to_path)
36     return response if io.nil?
38     headers = HeaderHash.new(headers)
39     st = io.stat
40     fileno = io.fileno
41     FD_MAP[fileno] = io
42     if st.file?
43       headers['Content-Length'] ||= st.size.to_s
44       headers.delete('Transfer-Encoding')
45     elsif st.pipe? || st.socket? # epoll-able things
46       unless headers['Content-Length']
47         if env['rainbows.autochunk']
48           headers['Transfer-Encoding'] = 'chunked'
49         else
50           headers['X-Rainbows-Autochunk'] = 'no'
51         end
52       end
54       # we need to make sure our pipe output is Fiber-compatible
55       case env["rainbows.model"]
56       when :FiberSpawn, :FiberPool, :RevFiberSpawn
57         io.respond_to?(:kgio_wait_readable) or
58           io = Rainbows::Fiber::IO.new(io)
59       when :Revactor
60         io = Rainbows::Revactor::Proxy.new(io)
61       end
62     else # unlikely, char/block device file, directory, ...
63       return response
64     end
65     [ status, headers, Body.new(io, "/dev/fd/#{fileno}", body) ]
66   end
68   class Body < Struct.new(:to_io, :to_path, :orig_body)
69     # called by the webserver or other middlewares if they can't
70     # handle #to_path
71     def each(&block)
72       to_io.each(&block)
73     end
75     # remain Rack::Lint-compatible for people with wonky systems :P
76     unless File.directory?("/dev/fd")
77       alias to_path_orig to_path
78       undef_method :to_path
79     end
81     # called by the web server after #each
82     def close
83       to_io.close unless to_io.closed?
84       orig_body.close if orig_body.respond_to?(:close) # may not be an IO
85     rescue IOError # could've been IO::new()'ed and closed
86     end
87   end
88   #:startdoc:
89 end # class