switch from IO#sendfile_nonblock to IO#trysendfile
[rainbows.git] / lib / rainbows / response.rb
blob576ff8d466273d92a70601535fc8bf50e326a44c
1 # -*- encoding: binary -*-
2 # :enddoc:
3 module Rainbows::Response
4   include Unicorn::HttpResponse
5   Close = "close"
6   KeepAlive = "keep-alive"
7   Content_Length = "Content-Length".freeze
8   Transfer_Encoding = "Transfer-Encoding".freeze
10   # private file class for IO objects opened by Rainbows! itself (and not
11   # the app or middleware)
12   class F < File; end
14   # called after forking
15   def self.setup(klass)
16     Kgio.accept_class = Rainbows::Client
17     0 == Rainbows.keepalive_timeout and
18       Rainbows::HttpParser.keepalive_requests = 0
19   end
21   def write_headers(status, headers, alive)
22     @hp.headers? or return
23     status = CODES[status.to_i] || status
24     buf = "HTTP/1.1 #{status}\r\n" \
25           "Date: #{httpdate}\r\n" \
26           "Status: #{status}\r\n" \
27           "Connection: #{alive ? KeepAlive : Close}\r\n"
28     headers.each do |key, value|
29       next if %r{\A(?:Date\z|Connection\z)}i =~ key
30       if value =~ /\n/
31         # avoiding blank, key-only cookies with /\n+/
32         buf << value.split(/\n+/).map! { |v| "#{key}: #{v}\r\n" }.join
33       else
34         buf << "#{key}: #{value}\r\n"
35       end
36     end
37     write(buf << CRLF)
38   end
40   def close_if_private(io)
41     io.close if F === io
42   end
44   def io_for_fd(fd)
45     Rainbows::FD_MAP.delete(fd) || F.for_fd(fd)
46   end
48   # to_io is not part of the Rack spec, but make an exception here
49   # since we can conserve path lookups and file descriptors.
50   # \Rainbows! will never get here without checking for the existence
51   # of body.to_path first.
52   def body_to_io(body)
53     if body.respond_to?(:to_io)
54       body.to_io
55     else
56       # try to take advantage of Rainbows::DevFdResponse, calling F.open
57       # is a last resort
58       path = body.to_path
59       %r{\A/dev/fd/(\d+)\z} =~ path ? io_for_fd($1.to_i) : F.open(path)
60     end
61   end
63   module Each
64     # generic body writer, used for most dynamically-generated responses
65     def write_body_each(body)
66       body.each { |chunk| write(chunk) }
67     end
69     # generic response writer, used for most dynamically-generated responses
70     # and also when IO.copy_stream and/or IO#trysendfile is unavailable
71     def write_response(status, headers, body, alive)
72       write_headers(status, headers, alive)
73       write_body_each(body)
74       ensure
75         body.close if body.respond_to?(:close)
76     end
77   end
78   include Each
80   if IO.method_defined?(:trysendfile)
81     module Sendfile
82       def write_body_file(body, range)
83         io = body_to_io(body)
84         range ? sendfile(io, range[0], range[1]) : sendfile(io, 0)
85         ensure
86           close_if_private(io)
87       end
88     end
89     include Sendfile
90   end
92   if IO.respond_to?(:copy_stream)
93     unless IO.method_defined?(:trysendfile)
94       module CopyStream
95         def write_body_file(body, range)
96           range ? IO.copy_stream(body, self, range[1], range[0]) :
97                   IO.copy_stream(body, self, nil, 0)
98         end
99       end
100       include CopyStream
101     end
103     # write_body_stream is an alias for write_body_each if IO.copy_stream
104     # isn't used or available.
105     def write_body_stream(body)
106       IO.copy_stream(io = body_to_io(body), self)
107       ensure
108         close_if_private(io)
109     end
110   else # ! IO.respond_to?(:copy_stream)
111     alias write_body_stream write_body_each
112   end  # ! IO.respond_to?(:copy_stream)
114   if IO.method_defined?(:trysendfile) || IO.respond_to?(:copy_stream)
115     HTTP_RANGE = 'HTTP_RANGE'
116     Content_Range = 'Content-Range'.freeze
118     # This does not support multipart responses (does anybody actually
119     # use those?)
120     def sendfile_range(status, headers)
121       200 == status.to_i &&
122       /\Abytes=(\d+-\d*|\d*-\d+)\z/ =~ @hp.env[HTTP_RANGE] or
123         return
124       a, b = $1.split(/-/)
126       # HeaderHash is quite expensive, and Rack::File currently
127       # uses a regular Ruby Hash with properly-cased headers the
128       # same way they're presented in rfc2616.
129       headers = Rack::Utils::HeaderHash.new(headers) unless Hash === headers
130       clen = headers[Content_Length] or return
131       size = clen.to_i
133       if b.nil? # bytes=M-
134         offset = a.to_i
135         count = size - offset
136       elsif a.empty? # bytes=-N
137         offset = size - b.to_i
138         count = size - offset
139       else  # bytes=M-N
140         offset = a.to_i
141         count = b.to_i + 1 - offset
142       end
144       if 0 > count || offset >= size
145         headers[Content_Length] = "0"
146         headers[Content_Range] = "bytes */#{clen}"
147         return 416, headers, nil
148       else
149         count = size if count > size
150         headers[Content_Length] = count.to_s
151         headers[Content_Range] = "bytes #{offset}-#{offset+count-1}/#{clen}"
152         return 206, headers, [ offset, count ]
153       end
154     end
156     def write_response_path(status, headers, body, alive)
157       if File.file?(body.to_path)
158         if r = sendfile_range(status, headers)
159           status, headers, range = r
160           write_headers(status, headers, alive)
161           write_body_file(body, range) if range
162         else
163           write_headers(status, headers, alive)
164           write_body_file(body, nil)
165         end
166       else
167         write_headers(status, headers, alive)
168         write_body_stream(body)
169       end
170       ensure
171         body.close if body.respond_to?(:close)
172     end
174     module ToPath
175       def write_response(status, headers, body, alive)
176         if body.respond_to?(:to_path)
177           write_response_path(status, headers, body, alive)
178         else
179           super
180         end
181       end
182     end
183     include ToPath
184   end # IO.respond_to?(:copy_stream) || IO.method_defined?(:trysendfile)