dev_fd_response: cleanup and reorganization
[rainbows.git] / lib / rainbows / dev_fd_response.rb
blob0489fd6a1a41854d4af48c0fbe1a18828c2a578f
1 # -*- encoding: binary -*-
3 module Rainbows
5   # Rack response middleware wrapping any IO-like object with an
6   # OS-level file descriptor associated with it.  May also be used to
7   # create responses from integer file descriptors or existing +IO+
8   # objects.  This may be used in conjunction with the #to_path method
9   # on servers that support it to pass arbitrary file descriptors into
10   # the HTTP response without additional open(2) syscalls
12   class DevFdResponse < Struct.new(:app)
13     # :stopdoc:
14     include Rack::Utils
16     # Rack middleware entry point, we'll just pass through responses
17     # unless they respond to +to_io+ or +to_path+
18     def call(env)
19       status, headers, body = response = app.call(env)
21       # totally uninteresting to us if there's no body
22       return response if STATUS_WITH_NO_ENTITY_BODY.include?(status)
24       io = body.to_io if body.respond_to?(:to_io)
25       io ||= File.open(body.to_path, 'rb') if body.respond_to?(:to_path)
26       return response if io.nil?
28       headers = HeaderHash.new(headers)
29       st = io.stat
30       if st.file?
31         headers['Content-Length'] ||= st.size.to_s
32         headers.delete('Transfer-Encoding')
33       elsif st.pipe? || st.socket? # epoll-able things
34         if env['rainbows.autochunk']
35           headers['Transfer-Encoding'] = 'chunked'
36           headers.delete('Content-Length')
37         else
38           headers['X-Rainbows-Autochunk'] = 'no'
39         end
41         # we need to make sure our pipe output is Fiber-compatible
42         case env["rainbows.model"]
43         when :FiberSpawn, :FiberPool, :RevFiberSpawn
44           return [ status, headers, Fiber::IO.new(io,::Fiber.current) ]
45         end
46       else # unlikely, char/block device file, directory, ...
47         return response
48       end
49       [ status, headers, Body.new(io, "/dev/fd/#{io.fileno}") ]
50     end
52     class Body < Struct.new(:to_io, :to_path)
53       # called by the webserver or other middlewares if they can't
54       # handle #to_path
55       def each(&block)
56         to_io.each(&block)
57       end
59       # remain Rack::Lint-compatible for people with wonky systems :P
60       unless File.exist?("/dev/fd/0")
61         alias to_path_orig to_path
62         undef_method :to_path
63       end
65       # called by the web server after #each
66       def close
67         begin
68           to_io.close if to_io.respond_to?(:close)
69         rescue IOError # could've been IO::new()'ed and closed
70         end
71       end
72     end
73     #:startdoc:
74   end # class
75 end