send proper 416 responses
[rainbows.git] / lib / rainbows / response.rb
blob705de626a7712393ca304adad044a607724b7528
1 # -*- encoding: binary -*-
2 # :enddoc:
3 require 'time' # for Time#httpdate
5 module Rainbows::Response
6   CRLF = Unicorn::HttpResponse::CRLF
7   CODES = Unicorn::HttpResponse::CODES
8   Close = "close"
9   KeepAlive = "keep-alive"
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(klass)
17     Kgio.accept_class = Rainbows::Client
18     0 == Rainbows::G.kato and Rainbows::HttpParser.keepalive_requests = 0
19   end
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
30       if value =~ /\n/
31         # avoiding blank, key-only cookies with /\n+/
32         buf << value.split(/\n+/).map! { |v| "#{key}: #{v}\r\n" }.join
33       else
34         buf << "#{key}: #{value}\r\n"
35       end
36     end
37     write(buf << CRLF)
38   end
40   def close_if_private(io)
41     io.close if F === io
42   end
44   def io_for_fd(fd)
45     Rainbows::FD_MAP.delete(fd) || F.for_fd(fd)
46   end
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.
52   def body_to_io(body)
53     if body.respond_to?(:to_io)
54       body.to_io
55     else
56       # try to take advantage of Rainbows::DevFdResponse, calling F.open
57       # is a last resort
58       path = body.to_path
59       %r{\A/dev/fd/(\d+)\z} =~ path ? io_for_fd($1.to_i) : F.open(path)
60     end
61   end
63   module Each
64     # generic body writer, used for most dynamically-generated responses
65     def write_body_each(body)
66       body.each { |chunk| write(chunk) }
67     end
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)
73       write_body_each(body)
74       ensure
75         body.close if body.respond_to?(:close)
76     end
77   end
78   include Each
80   if IO.method_defined?(:sendfile_nonblock)
81     module Sendfile
82       def write_body_file(body, range)
83         io = body_to_io(body)
84         range ? sendfile(io, range[0], range[1]) : sendfile(io, 0)
85         ensure
86           close_if_private(io)
87       end
88     end
89     include Sendfile
90   end
92   if IO.respond_to?(:copy_stream)
93     unless IO.method_defined?(:sendfile_nonblock)
94       module CopyStream
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)
98         end
99       end
100       include CopyStream
101     end
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)
107       ensure
108         close_if_private(io)
109     end
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
120     # use those?)
121     def sendfile_range(status, headers)
122       200 == status.to_i &&
123       /\Abytes=(\d+-\d*|\d*-\d+)\z/ =~ @hp.env[HTTP_RANGE] or
124         return
125       a, b = $1.split(/-/)
127       headers = Rack::Utils::HeaderHash.new(headers)
128       clen = headers[Content_Length] or return
129       size = clen.to_i
131       if b.nil? # bytes=M-
132         offset = a.to_i
133         count = size - offset
134       elsif a.empty? # bytes=-N
135         offset = size - b.to_i
136         count = size - offset
137       else  # bytes=M-N
138         offset = a.to_i
139         count = b.to_i + 1 - offset
140       end
142       if 0 > count || offset >= size
143         headers[Content_Length] = "0"
144         headers[Content_Range] = "bytes */#{clen}"
145         return 416, headers, nil
146       else
147         count = size if count > size
148         headers[Content_Length] = count.to_s
149         headers[Content_Range] = "bytes #{offset}-#{offset+count-1}/#{clen}"
150         return 206, headers, [ offset, count ]
151       end
152     end
154     def write_response_path(status, headers, body, alive)
155       if File.file?(body.to_path)
156         if r = sendfile_range(status, headers)
157           status, headers, range = r
158           write_headers(status, headers, alive)
159           write_body_file(body, range) if range
160         else
161           write_headers(status, headers, alive)
162           write_body_file(body, nil)
163         end
164       else
165         write_headers(status, headers, alive)
166         write_body_stream(body)
167       end
168       ensure
169         body.close if body.respond_to?(:close)
170     end
172     module ToPath
173       def write_response(status, headers, body, alive)
174         if body.respond_to?(:to_path)
175           write_response_path(status, headers, body, alive)
176         else
177           super
178         end
179       end
180     end
181     include ToPath
182   end # IO.respond_to?(:copy_stream) || IO.method_defined?(:sendfile_nonblock)