response: simplify regexp
[rainbows.git] / lib / rainbows / response.rb
blob62dfa393fb06440f0b6f68b4f07957bcc1d7157f
1 # -*- encoding: binary -*-
2 # :enddoc:
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)
9   class F < File; end
11   # called after forking
12   def self.setup
13     Kgio.accept_class = Rainbows::Client
14     0 == Rainbows.server.keepalive_timeout and
15       Rainbows::HttpParser.keepalive_requests = 0
16   end
18   def hijack_socket
19     @hp.env['rack.hijack'].call
20   end
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
26     hijack = nil
27     code = status.to_i
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|
32       case key
33       when %r{\A(?:Date|Connection)\z}i
34         next
35       when "rack.hijack"
36         # this was an illegal key in Rack < 1.5, so it should be
37         # OK to silently discard it for those older versions
38         hijack = value
39         alive = false # No persistent connections for hijacking
40       else
41         if /\n/ =~ value
42           # avoiding blank, key-only cookies with /\n+/
43           value.split(/\n+/).each { |v| buf << "#{key}: #{v}\r\n" }
44         else
45           buf << "#{key}: #{value}\r\n"
46         end
47       end
48     end
49     write(buf << (alive ? "Connection: keep-alive\r\n\r\n".freeze
50                         : "Connection: close\r\n\r\n".freeze))
52     if hijack
53       body = nil # ensure caller does not close body
54       hijack.call(hijack_socket)
55     end
56     body
57   end
59   def close_if_private(io)
60     io.close if F === io
61   end
63   def io_for_fd(fd)
64     Rainbows::FD_MAP.delete(fd) || F.for_fd(fd)
65   end
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.
71   def body_to_io(body)
72     if body.respond_to?(:to_io)
73       body.to_io
74     else
75       # try to take advantage of Rainbows::DevFdResponse, calling F.open
76       # is a last resort
77       path = body.to_path
78       %r{\A/dev/fd/(\d+)\z} =~ path ? io_for_fd($1.to_i) : F.open(path)
79     end
80   end
82   module Each
83     # generic body writer, used for most dynamically-generated responses
84     def write_body_each(body)
85       body.each { |chunk| write(chunk) }
86     end
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
93       body
94       ensure
95         body.close if body.respond_to?(:close)
96     end
97   end
98   include Each
100   if IO.method_defined?(:trysendfile)
101     module Sendfile
102       def write_body_file(body, range)
103         io = body_to_io(body)
104         range ? sendfile(io, range[0], range[1]) : sendfile(io, 0)
105         ensure
106           close_if_private(io)
107       end
108     end
109     include Sendfile
110   end
112   if COPY_STREAM
113     unless IO.method_defined?(:trysendfile)
114       module CopyStream
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)
118             body = body.to_path
119           end
121           range ? COPY_STREAM.copy_stream(body, self, range[1], range[0]) :
122                   COPY_STREAM.copy_stream(body, self)
123         end
124       end
125       include CopyStream
126     end
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)
132       ensure
133         close_if_private(io)
134     end
135   else # ! COPY_STREAM
136     alias write_body_stream write_body_each
137   end  # ! COPY_STREAM
139   if IO.method_defined?(:trysendfile) || COPY_STREAM
140     # This does not support multipart responses (does anybody actually
141     # use those?)
142     def sendfile_range(status, headers)
143       status = status.to_i
144       if 206 == status
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 ]
148         end
149         return # wtf...
150       end
151       200 == status &&
152       /\Abytes=(\d+-\d*|\d*-\d+)\z/ =~ @hp.env['HTTP_RANGE'] or
153         return
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
161       size = clen.to_i
163       if b.nil? # bytes=M-
164         offset = a.to_i
165         count = size - offset
166       elsif a.empty? # bytes=-N
167         offset = size - b.to_i
168         count = size - offset
169       else  # bytes=M-N
170         offset = a.to_i
171         count = b.to_i + 1 - offset
172       end
174       if 0 > count || offset >= size
175         headers['Content-Length'.freeze] = "0"
176         headers['Content-Range'.freeze] = "bytes */#{clen}"
177         return 416, headers, nil
178       else
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 ]
184       end
185     end
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
193         else
194           body = write_headers(status, headers, alive, body)
195           write_body_file(body, nil) if body
196         end
197       else
198         body = write_headers(status, headers, alive, body)
199         write_body_stream(body) if body
200       end
201       body
202       ensure
203         body.close if body.respond_to?(:close)
204     end
206     module ToPath
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)
211         else
212           super
213         end
214       end
215     end
216     include ToPath
217   end # COPY_STREAM || IO.method_defined?(:trysendfile)