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