Rainbows! 5.2.1
[rainbows.git] / lib / rainbows / dev_fd_response.rb
blob8ebaabd25e61f1037080ac8425a134abe4cd46e8
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 class Rainbows::DevFdResponse < Struct.new(:app)
12   # :stopdoc:
13   FD_MAP = Rainbows::FD_MAP
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     if STATUS_WITH_NO_ENTITY_BODY.include?(status.to_i) ||
23        File === body ||
24        (body.respond_to?(:to_path) && File.file?(body.to_path))
25       return response
26     end
28     io = body.to_io if body.respond_to?(:to_io)
29     io ||= File.open(body.to_path) if body.respond_to?(:to_path)
30     return response if io.nil?
32     headers = Rack::Utils::HeaderHash.new(headers) unless Hash === headers
33     st = io.stat
34     fileno = io.fileno
35     FD_MAP[fileno] = io
36     if st.file?
37       headers['Content-Length'.freeze] ||= st.size.to_s
38       headers.delete('Transfer-Encoding'.freeze)
39     elsif st.pipe? || st.socket? # epoll-able things
40       unless headers.include?('Content-Length'.freeze)
41         if env['rainbows.autochunk']
42           case env['HTTP_VERSION']
43           when "HTTP/1.0", nil
44           else
45             headers['Transfer-Encoding'.freeze] = 'chunked'
46           end
47         else
48           env['rainbows.autochunk'] = false
49         end
50       end
52       # we need to make sure our pipe output is Fiber-compatible
53       case env['rainbows.model']
54       when :FiberSpawn, :FiberPool, :RevFiberSpawn, :CoolioFiberSpawn
55         io.respond_to?(:kgio_wait_readable) or
56           io = Rainbows::Fiber::IO.new(io)
57       when :Revactor
58         io = Rainbows::Revactor::Proxy.new(io)
59       end
60     else # unlikely, char/block device file, directory, ...
61       return response
62     end
63     [ status, headers, Body.new(io, "/dev/fd/#{fileno}", body) ]
64   end
66   class Body < Struct.new(:to_io, :to_path, :orig_body) # :nodoc:
67     # called by the webserver or other middlewares if they can't
68     # handle #to_path
69     def each
70       to_io.each { |x| yield x }
71     end
73     # remain Rack::Lint-compatible for people with wonky systems :P
74     unless File.directory?("/dev/fd")
75       alias to_path_orig to_path
76       undef_method :to_path
77     end
79     # called by the web server after #each
80     def close
81       to_io.close unless to_io.closed?
82       orig_body.close if orig_body.respond_to?(:close) # may not be an IO
83     rescue IOError # could've been IO::new()'ed and closed
84     end
85   end
86   #:startdoc:
87 end # class