several response body#close fixes
[rainbows.git] / lib / rainbows / response / body.rb
blobe80217d8e833d80532447384a351926f9509bf66
1 # -*- encoding: binary -*-
2 # :enddoc:
3 # non-portable body response stuff goes here
5 # The sendfile 1.0.0 RubyGem includes IO#sendfile and
6 # IO#sendfile_nonblock.   Previous versions of "sendfile" didn't have
7 # IO#sendfile_nonblock, and IO#sendfile in previous versions could
8 # block other threads under 1.8 with large files
10 # IO#sendfile currently (June 2010) beats 1.9 IO.copy_stream with
11 # non-Linux support and large files on 32-bit.  We still fall back to
12 # IO.copy_stream (if available) if we're dealing with DevFdResponse
13 # objects, though.
15 # Linux-only splice(2) support via the "io_splice" gem will eventually
16 # be added for streaming sockets/pipes, too.
18 # * write_body_file - regular files (sendfile or pread+write)
19 # * write_body_stream - socket/pipes (read+write, splice later)
20 # * write_body_each - generic fallback
22 # callgraph is as follows:
24 #         write_body
25 #         `- write_body_each
26 #         `- write_body_path
27 #            `- write_body_file
28 #            `- write_body_stream
30 module Rainbows::Response::Body # :nodoc:
31   ALIASES = {}
33   FD_MAP = Rainbows::FD_MAP
35   class F < File; end
37   def close_if_private(io)
38     io.close if F === io
39   end
41   def io_for_fd(fd)
42     FD_MAP.delete(fd) || F.for_fd(fd)
43   end
45   # to_io is not part of the Rack spec, but make an exception here
46   # since we can conserve path lookups and file descriptors.
47   # \Rainbows! will never get here without checking for the existence
48   # of body.to_path first.
49   def body_to_io(body)
50     if body.respond_to?(:to_io)
51       body.to_io
52     else
53       # try to take advantage of Rainbows::DevFdResponse, calling File.open
54       # is a last resort
55       path = body.to_path
56       path =~ %r{\A/dev/fd/(\d+)\z} ? io_for_fd($1.to_i) : F.open(path)
57     end
58   end
60   if IO.method_defined?(:sendfile_nonblock)
61     def write_body_file(sock, body, range)
62       io = body_to_io(body)
63       range ? sock.sendfile(io, range[0], range[1]) : sock.sendfile(io, 0)
64       ensure
65         close_if_private(io)
66     end
67   end
69   if IO.respond_to?(:copy_stream)
70     unless method_defined?(:write_body_file)
71       # try to use sendfile() via IO.copy_stream, otherwise pread()+write()
72       def write_body_file(sock, body, range)
73         range ? IO.copy_stream(body, sock, range[1], range[0]) :
74                 IO.copy_stream(body, sock, nil, 0)
75       end
76     end
78     # only used when body is a pipe or socket that can't handle
79     # pread() semantics
80     def write_body_stream(sock, body, range)
81       IO.copy_stream(body, sock)
82     end
83   else
84     # fall back to body#each, which is a Rack standard
85     ALIASES[:write_body_stream] = :write_body_each
86   end
88   if method_defined?(:write_body_file)
89     # middlewares/apps may return with a body that responds to +to_path+
90     def write_body_path(sock, body, range)
91       stat = File.stat(body.to_path)
92       stat.file? ? write_body_file(sock, body, range) :
93                    write_body_stream(sock, body, range)
94       ensure
95         body.respond_to?(:close) and body.close
96     end
97   elsif method_defined?(:write_body_stream)
98     def write_body_path(sock, body, range)
99       write_body_stream(sock, body, range)
100       ensure
101         body.respond_to?(:close) and body.close
102     end
103   end
105   if method_defined?(:write_body_path)
106     def write_body(client, body, range)
107       body.respond_to?(:to_path) ?
108         write_body_path(client, body, range) :
109         write_body_each(client, body, range)
110     end
111   else
112     ALIASES[:write_body] = :write_body_each
113   end
115   # generic body writer, used for most dynamically generated responses
116   def write_body_each(socket, body, range = nil)
117     body.each { |chunk| socket.write(chunk) }
118     ensure
119       body.respond_to?(:close) and body.close
120   end
122   def self.included(klass)
123     ALIASES.each do |new_method, orig_method|
124       klass.__send__(:alias_method, new_method, orig_method)
125     end
126   end