f8b0831eba2fcfca7fcaff2fcc3aa45358520712
[rainbows.git] / lib / rainbows / response.rb
blobf8b0831eba2fcfca7fcaff2fcc3aa45358520712
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   def write_headers(status, headers, alive)
23     @hp.headers? or return
24     status = CODES[status.to_i] || status
25     buf = "HTTP/1.1 #{status}\r\n" \
26           "Date: #{httpdate}\r\n" \
27           "Status: #{status}\r\n" \
28           "Connection: #{alive ? KeepAlive : Close}\r\n"
29     headers.each do |key, value|
30       next if %r{\A(?:Date\z|Connection\z)}i =~ key
31       if value =~ /\n/
32         # avoiding blank, key-only cookies with /\n+/
33         buf << value.split(/\n+/).map! { |v| "#{key}: #{v}\r\n" }.join
34       else
35         buf << "#{key}: #{value}\r\n"
36       end
37     end
38     write(buf << CRLF)
39   end
41   def close_if_private(io)
42     io.close if F === io
43   end
45   def io_for_fd(fd)
46     Rainbows::FD_MAP.delete(fd) || F.for_fd(fd)
47   end
49   # to_io is not part of the Rack spec, but make an exception here
50   # since we can conserve path lookups and file descriptors.
51   # \Rainbows! will never get here without checking for the existence
52   # of body.to_path first.
53   def body_to_io(body)
54     if body.respond_to?(:to_io)
55       body.to_io
56     else
57       # try to take advantage of Rainbows::DevFdResponse, calling F.open
58       # is a last resort
59       path = body.to_path
60       %r{\A/dev/fd/(\d+)\z} =~ path ? io_for_fd($1.to_i) : F.open(path)
61     end
62   end
64   module Each
65     # generic body writer, used for most dynamically-generated responses
66     def write_body_each(body)
67       body.each { |chunk| write(chunk) }
68     end
70     # generic response writer, used for most dynamically-generated responses
71     # and also when copy_stream and/or IO#trysendfile is unavailable
72     def write_response(status, headers, body, alive)
73       write_headers(status, headers, alive)
74       write_body_each(body)
75       ensure
76         body.close if body.respond_to?(:close)
77     end
78   end
79   include Each
81   if IO.method_defined?(:trysendfile)
82     module Sendfile
83       def write_body_file(body, range)
84         io = body_to_io(body)
85         range ? sendfile(io, range[0], range[1]) : sendfile(io, 0)
86         ensure
87           close_if_private(io)
88       end
89     end
90     include Sendfile
91   end
93   if COPY_STREAM
94     unless IO.method_defined?(:trysendfile)
95       module CopyStream
96         def write_body_file(body, range)
97           range ? COPY_STREAM.copy_stream(body, self, range[1], range[0]) :
98                   COPY_STREAM.copy_stream(body, self, nil, 0)
99         end
100       end
101       include CopyStream
102     end
104     # write_body_stream is an alias for write_body_each if copy_stream
105     # isn't used or available.
106     def write_body_stream(body)
107       COPY_STREAM.copy_stream(io = body_to_io(body), self)
108       ensure
109         close_if_private(io)
110     end
111   else # ! COPY_STREAM
112     alias write_body_stream write_body_each
113   end  # ! COPY_STREAM
115   if IO.method_defined?(:trysendfile) || COPY_STREAM
116     HTTP_RANGE = 'HTTP_RANGE'
117     Content_Range = 'Content-Range'.freeze
119     # This does not support multipart responses (does anybody actually
120     # use those?)
121     def sendfile_range(status, headers)
122       status = status.to_i
123       if 206 == status
124         if %r{\Abytes (\d+)-(\d+)/\d+\z} =~ headers[Content_Range]
125           a, b = $1.to_i, $2.to_i
126           return 206, headers, [ a,  b - a + 1 ]
127         end
128         return # wtf...
129       end
130       200 == status &&
131       /\Abytes=(\d+-\d*|\d*-\d+)\z/ =~ @hp.env[HTTP_RANGE] or
132         return
133       a, b = $1.split(/-/)
135       # HeaderHash is quite expensive, and Rack::File currently
136       # uses a regular Ruby Hash with properly-cased headers the
137       # same way they're presented in rfc2616.
138       headers = Rack::Utils::HeaderHash.new(headers) unless Hash === headers
139       clen = headers[Content_Length] or return
140       size = clen.to_i
142       if b.nil? # bytes=M-
143         offset = a.to_i
144         count = size - offset
145       elsif a.empty? # bytes=-N
146         offset = size - b.to_i
147         count = size - offset
148       else  # bytes=M-N
149         offset = a.to_i
150         count = b.to_i + 1 - offset
151       end
153       if 0 > count || offset >= size
154         headers[Content_Length] = "0"
155         headers[Content_Range] = "bytes */#{clen}"
156         return 416, headers, nil
157       else
158         count = size if count > size
159         headers[Content_Length] = count.to_s
160         headers[Content_Range] = "bytes #{offset}-#{offset+count-1}/#{clen}"
161         return 206, headers, [ offset, count ]
162       end
163     end
165     def write_response_path(status, headers, body, alive)
166       if File.file?(body.to_path)
167         if r = sendfile_range(status, headers)
168           status, headers, range = r
169           write_headers(status, headers, alive)
170           write_body_file(body, range) if range
171         else
172           write_headers(status, headers, alive)
173           write_body_file(body, nil)
174         end
175       else
176         write_headers(status, headers, alive)
177         write_body_stream(body)
178       end
179       ensure
180         body.close if body.respond_to?(:close)
181     end
183     module ToPath
184       def write_response(status, headers, body, alive)
185         if body.respond_to?(:to_path)
186           write_response_path(status, headers, body, alive)
187         else
188           super
189         end
190       end
191     end
192     include ToPath
193   end # COPY_STREAM || IO.method_defined?(:trysendfile)