1 # -*- encoding: binary -*-
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)
16 # Rack middleware entry point, we'll just pass through responses
17 # unless they respond to +to_io+ or +to_path+
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)
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')
38 headers['X-Rainbows-Autochunk'] = 'no'
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) ]
46 else # unlikely, char/block device file, directory, ...
49 [ status, headers, Body.new(io, "/dev/fd/#{io.fileno}") ]
52 class Body < Struct.new(:to_io, :to_path)
53 # called by the webserver or other middlewares if they can't
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
65 # called by the web server after #each
68 to_io.close if to_io.respond_to?(:close)
69 rescue IOError # could've been IO::new()'ed and closed