1 # -*- encoding: binary -*-
3 require 'time' # for Time#httpdate
5 module Rainbows::Response
6 CRLF = Unicorn::HttpResponse::CRLF
7 CODES = Unicorn::HttpResponse::CODES
9 KeepAlive = "keep-alive"
11 # private file class for IO objects opened by Rainbows! itself (and not
12 # the app or middleware)
15 # called after forking
17 Kgio.accept_class = Rainbows::Client
18 0 == Rainbows::G.kato and Rainbows::HttpParser.keepalive_requests = 0
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: #{Time.now.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(?:X-Rainbows-|Date\z|Status\z\|Connection\z)}i =~ key
31 # avoiding blank, key-only cookies with /\n+/
32 buf << value.split(/\n+/).map! { |v| "#{key}: #{v}\r\n" }.join
34 buf << "#{key}: #{value}\r\n"
40 def close_if_private(io)
45 Rainbows::FD_MAP.delete(fd) || F.for_fd(fd)
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.
53 if body.respond_to?(:to_io)
56 # try to take advantage of Rainbows::DevFdResponse, calling F.open
59 %r{\A/dev/fd/(\d+)\z} =~ path ? io_for_fd($1.to_i) : F.open(path)
64 # generic body writer, used for most dynamically-generated responses
65 def write_body_each(body)
66 body.each { |chunk| write(chunk) }
69 # generic response writer, used for most dynamically-generated responses
70 # and also when IO.copy_stream and/or IO#sendfile_nonblock is unavailable
71 def write_response(status, headers, body, alive)
72 write_headers(status, headers, alive)
75 body.close if body.respond_to?(:close)
80 if IO.method_defined?(:sendfile_nonblock)
82 def write_body_file(body, range)
84 range ? sendfile(io, range[0], range[1]) : sendfile(io, 0)
92 if IO.respond_to?(:copy_stream)
93 unless IO.method_defined?(:sendfile_nonblock)
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)
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)
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?(:sendfile_nonblock) || IO.respond_to?(:copy_stream)
115 HTTP_RANGE = 'HTTP_RANGE'
116 Content_Range = 'Content-Range'.freeze
117 Content_Length = 'Content-Length'.freeze
119 # This does not support multipart responses (does anybody actually
121 def sendfile_range(status, headers)
122 200 == status.to_i &&
123 /\Abytes=(\d+-\d*|\d*-\d+)\z/ =~ @hp.env[HTTP_RANGE] or
127 headers = Rack::Utils::HeaderHash.new(headers)
128 clen = headers[Content_Length] or return
133 count = size - offset
134 elsif a.empty? # bytes=-N
135 offset = size - b.to_i
136 count = size - offset
139 count = b.to_i + 1 - offset
142 if 0 > count || offset >= size
143 return 416, headers, nil
145 count = size if count > size
146 headers[Content_Length] = count.to_s
147 headers[Content_Range] = "bytes #{offset}-#{offset+count-1}/#{clen}"
148 return 206, headers, [ offset, count ]
152 def write_response_path(status, headers, body, alive)
153 if File.file?(body.to_path)
154 if r = sendfile_range(status, headers)
155 status, headers, range = r
156 write_headers(status, headers, alive)
157 write_body_file(body, range) if range
159 write_headers(status, headers, alive)
160 write_body_file(body, nil)
163 write_headers(status, headers, alive)
164 write_body_stream(body)
167 body.close if body.respond_to?(:close)
171 def write_response(status, headers, body, alive)
172 if body.respond_to?(:to_path)
173 write_response_path(status, headers, body, alive)
180 end # IO.respond_to?(:copy_stream) || IO.method_defined?(:sendfile_nonblock)