1 # -*- encoding: binary -*-
3 module Rainbows::Response
4 include Unicorn::HttpResponse
5 Rainbows.config!(self, :copy_stream)
7 # private file class for IO objects opened by Rainbows! itself (and not
8 # the app or middleware)
11 # called after forking
13 Kgio.accept_class = Rainbows::Client
14 0 == Rainbows.server.keepalive_timeout and
15 Rainbows::HttpParser.keepalive_requests = 0
19 @hp.env['rack.hijack'].call
22 # returns the original body on success
23 # returns nil if the headers hijacked the response body
24 def write_headers(status, headers, alive, body)
25 @hp.headers? or return body
28 msg = Rack::Utils::HTTP_STATUS_CODES[code]
29 buf = "HTTP/1.1 #{msg ? %Q(#{code} #{msg}) : status}\r\n" \
30 "Date: #{httpdate}\r\n"
31 headers.each do |key, value|
33 when %r{\A(?:Date|Connection)\z}i
36 # this was an illegal key in Rack < 1.5, so it should be
37 # OK to silently discard it for those older versions
39 alive = false # No persistent connections for hijacking
42 # avoiding blank, key-only cookies with /\n+/
43 value.split(/\n+/).each { |v| buf << "#{key}: #{v}\r\n" }
45 buf << "#{key}: #{value}\r\n"
49 write(buf << (alive ? "Connection: keep-alive\r\n\r\n".freeze
50 : "Connection: close\r\n\r\n".freeze))
53 body = nil # ensure caller does not close body
54 hijack.call(hijack_socket)
59 def close_if_private(io)
64 Rainbows::FD_MAP.delete(fd) || F.for_fd(fd)
67 # to_io is not part of the Rack spec, but make an exception here
68 # since we can conserve path lookups and file descriptors.
69 # \Rainbows! will never get here without checking for the existence
70 # of body.to_path first.
72 if body.respond_to?(:to_io)
75 # try to take advantage of Rainbows::DevFdResponse, calling F.open
78 %r{\A/dev/fd/(\d+)\z} =~ path ? io_for_fd($1.to_i) : F.open(path)
83 # generic body writer, used for most dynamically-generated responses
84 def write_body_each(body)
85 body.each { |chunk| write(chunk) }
88 # generic response writer, used for most dynamically-generated responses
89 # and also when copy_stream and/or IO#trysendfile is unavailable
90 def write_response(status, headers, body, alive)
91 body = write_headers(status, headers, alive, body)
92 write_body_each(body) if body
95 body.close if body.respond_to?(:close)
100 if IO.method_defined?(:trysendfile)
102 def write_body_file(body, range)
103 io = body_to_io(body)
104 range ? sendfile(io, range[0], range[1]) : sendfile(io, 0)
113 unless IO.method_defined?(:trysendfile)
115 def write_body_file(body, range)
116 # ensure sendfile gets used for SyncClose objects:
117 if !body.kind_of?(IO) && body.respond_to?(:to_path)
121 range ? COPY_STREAM.copy_stream(body, self, range[1], range[0]) :
122 COPY_STREAM.copy_stream(body, self)
128 # write_body_stream is an alias for write_body_each if copy_stream
129 # isn't used or available.
130 def write_body_stream(body)
131 COPY_STREAM.copy_stream(io = body_to_io(body), self)
136 alias write_body_stream write_body_each
139 if IO.method_defined?(:trysendfile) || COPY_STREAM
140 # This does not support multipart responses (does anybody actually
142 def sendfile_range(status, headers)
145 if %r{\Abytes (\d+)-(\d+)/\d+\z} =~ headers['Content-Range'.freeze]
146 a, b = $1.to_i, $2.to_i
147 return 206, headers, [ a, b - a + 1 ]
152 /\Abytes=(\d+-\d*|\d*-\d+)\z/ =~ @hp.env['HTTP_RANGE'] or
154 a, b = $1.split('-'.freeze)
156 # HeaderHash is quite expensive, and Rack::File currently
157 # uses a regular Ruby Hash with properly-cased headers the
158 # same way they're presented in rfc2616.
159 headers = Rack::Utils::HeaderHash.new(headers) unless Hash === headers
160 clen = headers['Content-Length'.freeze] or return
165 count = size - offset
166 elsif a.empty? # bytes=-N
167 offset = size - b.to_i
168 count = size - offset
171 count = b.to_i + 1 - offset
174 if 0 > count || offset >= size
175 headers['Content-Length'.freeze] = "0"
176 headers['Content-Range'.freeze] = "bytes */#{clen}"
177 return 416, headers, nil
179 count = size if count > size
180 headers['Content-Length'.freeze] = count.to_s
181 headers['Content-Range'.freeze] =
182 "bytes #{offset}-#{offset+count-1}/#{clen}"
183 return 206, headers, [ offset, count ]
187 def write_response_path(status, headers, body, alive)
188 if File.file?(body.to_path)
189 if r = sendfile_range(status, headers)
190 status, headers, range = r
191 body = write_headers(status, headers, alive, body)
192 write_body_file(body, range) if body && range
194 body = write_headers(status, headers, alive, body)
195 write_body_file(body, nil) if body
198 body = write_headers(status, headers, alive, body)
199 write_body_stream(body) if body
203 body.close if body.respond_to?(:close)
207 # returns nil if hijacked
208 def write_response(status, headers, body, alive)
209 if body.respond_to?(:to_path)
210 write_response_path(status, headers, body, alive)
217 end # COPY_STREAM || IO.method_defined?(:trysendfile)