epoll+xepoll: clarify intent of these concurrency options
[rainbows.git] / lib / rainbows / response.rb
blob8a0daf84638bc1773e07cc423ec601ca0855ab21
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
9   Rainbows.config!(self, :copy_stream)
11   # private file class for IO objects opened by Rainbows! itself (and not
12   # the app or middleware)
13   class F < File; end
15   # called after forking
16   def self.setup
17     Kgio.accept_class = Rainbows::Client
18     0 == Rainbows.server.keepalive_timeout and
19       Rainbows::HttpParser.keepalive_requests = 0
20   end
22   # Rack 1.5.0 (protocol version 1.2) adds response hijacking support
23   if ((Rack::VERSION[0] << 8) | Rack::VERSION[1]) >= 0x0102
24     RACK_HIJACK = "rack.hijack"
26     def hijack_prepare(value)
27       value
28     end
30     def hijack_socket
31       @hp.env[RACK_HIJACK].call
32     end
33   else
34     def hijack_prepare(_)
35     end
36   end
38   # returns the original body on success
39   # returns nil if the headers hijacked the response body
40   def write_headers(status, headers, alive, body)
41     @hp.headers? or return body
42     hijack = nil
43     status = CODES[status.to_i] || status
44     buf = "HTTP/1.1 #{status}\r\n" \
45           "Date: #{httpdate}\r\n" \
46           "Status: #{status}\r\n"
47     headers.each do |key, value|
48       case key
49       when %r{\A(?:Date\z|Connection\z)}i
50         next
51       when "rack.hijack"
52         # this was an illegal key in Rack < 1.5, so it should be
53         # OK to silently discard it for those older versions
54         hijack = hijack_prepare(value)
55         alive = false # No persistent connections for hijacking
56       else
57         if /\n/ =~ value
58           # avoiding blank, key-only cookies with /\n+/
59           buf << value.split(/\n+/).map! { |v| "#{key}: #{v}\r\n" }.join
60         else
61           buf << "#{key}: #{value}\r\n"
62         end
63       end
64     end
65     write(buf << "Connection: #{alive ? KeepAlive : Close}\r\n\r\n")
67     if hijack
68       body = nil # ensure caller does not close body
69       hijack.call(hijack_socket)
70     end
71     body
72   end
74   def close_if_private(io)
75     io.close if F === io
76   end
78   def io_for_fd(fd)
79     Rainbows::FD_MAP.delete(fd) || F.for_fd(fd)
80   end
82   # to_io is not part of the Rack spec, but make an exception here
83   # since we can conserve path lookups and file descriptors.
84   # \Rainbows! will never get here without checking for the existence
85   # of body.to_path first.
86   def body_to_io(body)
87     if body.respond_to?(:to_io)
88       body.to_io
89     else
90       # try to take advantage of Rainbows::DevFdResponse, calling F.open
91       # is a last resort
92       path = body.to_path
93       %r{\A/dev/fd/(\d+)\z} =~ path ? io_for_fd($1.to_i) : F.open(path)
94     end
95   end
97   module Each
98     # generic body writer, used for most dynamically-generated responses
99     def write_body_each(body)
100       body.each { |chunk| write(chunk) }
101     end
103     # generic response writer, used for most dynamically-generated responses
104     # and also when copy_stream and/or IO#trysendfile is unavailable
105     def write_response(status, headers, body, alive)
106       body = write_headers(status, headers, alive, body)
107       write_body_each(body) if body
108       body
109       ensure
110         body.close if body.respond_to?(:close)
111     end
112   end
113   include Each
115   if IO.method_defined?(:trysendfile)
116     module Sendfile
117       def write_body_file(body, range)
118         io = body_to_io(body)
119         range ? sendfile(io, range[0], range[1]) : sendfile(io, 0)
120         ensure
121           close_if_private(io)
122       end
123     end
124     include Sendfile
125   end
127   if COPY_STREAM
128     unless IO.method_defined?(:trysendfile)
129       module CopyStream
130         def write_body_file(body, range)
131           range ? COPY_STREAM.copy_stream(body, self, range[1], range[0]) :
132                   COPY_STREAM.copy_stream(body, self, nil, 0)
133         end
134       end
135       include CopyStream
136     end
138     # write_body_stream is an alias for write_body_each if copy_stream
139     # isn't used or available.
140     def write_body_stream(body)
141       COPY_STREAM.copy_stream(io = body_to_io(body), self)
142       ensure
143         close_if_private(io)
144     end
145   else # ! COPY_STREAM
146     alias write_body_stream write_body_each
147   end  # ! COPY_STREAM
149   if IO.method_defined?(:trysendfile) || COPY_STREAM
150     HTTP_RANGE = 'HTTP_RANGE'
151     Content_Range = 'Content-Range'.freeze
153     # This does not support multipart responses (does anybody actually
154     # use those?)
155     def sendfile_range(status, headers)
156       status = status.to_i
157       if 206 == status
158         if %r{\Abytes (\d+)-(\d+)/\d+\z} =~ headers[Content_Range]
159           a, b = $1.to_i, $2.to_i
160           return 206, headers, [ a,  b - a + 1 ]
161         end
162         return # wtf...
163       end
164       200 == status &&
165       /\Abytes=(\d+-\d*|\d*-\d+)\z/ =~ @hp.env[HTTP_RANGE] or
166         return
167       a, b = $1.split(/-/)
169       # HeaderHash is quite expensive, and Rack::File currently
170       # uses a regular Ruby Hash with properly-cased headers the
171       # same way they're presented in rfc2616.
172       headers = Rack::Utils::HeaderHash.new(headers) unless Hash === headers
173       clen = headers[Content_Length] or return
174       size = clen.to_i
176       if b.nil? # bytes=M-
177         offset = a.to_i
178         count = size - offset
179       elsif a.empty? # bytes=-N
180         offset = size - b.to_i
181         count = size - offset
182       else  # bytes=M-N
183         offset = a.to_i
184         count = b.to_i + 1 - offset
185       end
187       if 0 > count || offset >= size
188         headers[Content_Length] = "0"
189         headers[Content_Range] = "bytes */#{clen}"
190         return 416, headers, nil
191       else
192         count = size if count > size
193         headers[Content_Length] = count.to_s
194         headers[Content_Range] = "bytes #{offset}-#{offset+count-1}/#{clen}"
195         return 206, headers, [ offset, count ]
196       end
197     end
199     def write_response_path(status, headers, body, alive)
200       if File.file?(body.to_path)
201         if r = sendfile_range(status, headers)
202           status, headers, range = r
203           body = write_headers(status, headers, alive, body)
204           write_body_file(body, range) if body && range
205         else
206           body = write_headers(status, headers, alive, body)
207           write_body_file(body, nil) if body
208         end
209       else
210         body = write_headers(status, headers, alive, body)
211         write_body_stream(body) if body
212       end
213       body
214       ensure
215         body.close if body.respond_to?(:close)
216     end
218     module ToPath
219       # returns nil if hijacked
220       def write_response(status, headers, body, alive)
221         if body.respond_to?(:to_path)
222           write_response_path(status, headers, body, alive)
223         else
224           super
225         end
226       end
227     end
228     include ToPath
229   end # COPY_STREAM || IO.method_defined?(:trysendfile)