avoid needless HeaderHash#to_hash calls
[rainbows.git] / lib / rainbows / dev_fd_response.rb
blob479a6681f8dd2d891934b1ca1bef1b9dcc74283e
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, :to_io, :to_path)
13     include Rack::Utils
15     # Rack middleware entry point, we'll just pass through responses
16     # unless they respond to +to_io+ or +to_path+
17     def call(env)
18       status, headers, body = response = app.call(env)
20       # totally uninteresting to us if there's no body
21       return response if STATUS_WITH_NO_ENTITY_BODY.include?(status)
23       io = body.to_io if body.respond_to?(:to_io)
24       io ||= File.open(body.to_path, 'rb') if body.respond_to?(:to_path)
25       return response if io.nil?
27       headers = HeaderHash.new(headers)
28       st = io.stat
29       if st.file?
30         headers['Content-Length'] ||= st.size.to_s
31         headers.delete('Transfer-Encoding')
32       elsif st.pipe? || st.socket? # epoll-able things
33         if env['rainbows.autochunk']
34           headers['Transfer-Encoding'] = 'chunked'
35           headers.delete('Content-Length')
36         else
37           headers['X-Rainbows-Autochunk'] = 'no'
38         end
40         # we need to make sure our pipe output is Fiber-compatible
41         case env["rainbows.model"]
42         when :FiberSpawn, :FiberPool, :RevFiberSpawn
43           return [ status, headers, Fiber::IO.new(io,::Fiber.current) ]
44         end
45       else # unlikely, char/block device file, directory, ...
46         return response
47       end
48       resp = dup # be reentrant here
49       resp.to_path = "/dev/fd/#{io.fileno}"
50       resp.to_io = io
51       [ status, headers, resp ]
52     end
54     # called by the webserver or other middlewares if they can't
55     # handle #to_path
56     def each(&block)
57       to_io.each(&block)
58     end
60     # remain Rack::Lint-compatible for people with wonky systems :P
61     unless File.exist?("/dev/fd/0")
62       alias to_path_orig to_path
63       undef_method :to_path
64     end
66     # called by the web server after #each
67     def close
68       begin
69         to_io.close if to_io.respond_to?(:close)
70       rescue IOError # could've been IO::new()'ed and closed
71       end
72     end
74   end # class
75 end