Re-enable spec/library for full CI runs.
[rbx.git] / lib / webrick / httpresponse.rb
blobd0f232d1e166dbfd6e8e06de0e7f5ccdaed5578e
2 # httpresponse.rb -- HTTPResponse Class
4 # Author: IPR -- Internet Programming with Ruby -- writers
5 # Copyright (c) 2000, 2001 TAKAHASHI Masayoshi, GOTOU Yuuzou
6 # Copyright (c) 2002 Internet Programming with Ruby writers. All rights
7 # reserved.
9 # $IPR: httpresponse.rb,v 1.45 2003/07/11 11:02:25 gotoyuzo Exp $
11 require 'time'
12 require 'webrick/httpversion'
13 require 'webrick/htmlutils'
14 require 'webrick/httputils'
15 require 'webrick/httpstatus'
17 module WEBrick
18   class HTTPResponse
19     BUFSIZE = 1024*4
21     attr_reader :http_version, :status, :header
22     attr_reader :cookies
23     attr_accessor :reason_phrase
24     attr_accessor :body
26     attr_accessor :request_method, :request_uri, :request_http_version
27     attr_accessor :filename
28     attr_accessor :keep_alive
29     attr_reader :config, :sent_size
31     def initialize(config)
32       @config = config
33       @logger = config[:Logger]
34       @header = Hash.new
35       @status = HTTPStatus::RC_OK
36       @reason_phrase = nil
37       @http_version = HTTPVersion::convert(@config[:HTTPVersion])
38       @body = ''
39       @keep_alive = true
40       @cookies = []
41       @request_method = nil
42       @request_uri = nil
43       @request_http_version = @http_version  # temporary
44       @chunked = false
45       @filename = nil
46       @sent_size = 0
47     end
49     def status_line
50       "HTTP/#@http_version #@status #@reason_phrase #{CRLF}"
51     end
53     def status=(status)
54       @status = status
55       @reason_phrase = HTTPStatus::reason_phrase(status)
56     end
58     def [](field)
59       @header[field.downcase]
60     end
62     def []=(field, value)
63       @header[field.downcase] = value.to_s
64     end
66     def content_length
67       if len = self['content-length']
68         return Integer(len)
69       end
70     end
72     def content_length=(len)
73       self['content-length'] = len.to_s
74     end
76     def content_type
77       self['content-type']
78     end
80     def content_type=(type)
81       self['content-type'] = type
82     end
84     def each
85       @header.each{|k, v|  yield(k, v) }
86     end
88     def chunked?
89       @chunked
90     end
92     def chunked=(val)
93       @chunked = val ? true : false
94     end
96     def keep_alive?
97       @keep_alive
98     end
100     def send_response(socket)
101       begin
102         setup_header()
103         send_header(socket)
104         send_body(socket)
105       rescue Errno::EPIPE, Errno::ECONNRESET, Errno::ENOTCONN => ex
106         @logger.debug(ex)
107         @keep_alive = false
108       rescue Exception => ex
109         @logger.error(ex)
110         @keep_alive = false
111       end
112     end
114     def setup_header()
115       @reason_phrase    ||= HTTPStatus::reason_phrase(@status)
116       @header['server'] ||= @config[:ServerSoftware]
117       @header['date']   ||= Time.now.httpdate
119       # HTTP/0.9 features
120       if @request_http_version < "1.0"
121         @http_version = HTTPVersion.new("0.9")
122         @keep_alive = false
123       end
125       # HTTP/1.0 features
126       if @request_http_version < "1.1"
127         if chunked?
128           @chunked = false
129           ver = @request_http_version.to_s
130           msg = "chunked is set for an HTTP/#{ver} request. (ignored)"
131           @logger.warn(msg)
132         end
133       end
135       # Determin the message length (RFC2616 -- 4.4 Message Length)
136       if @status == 304 || @status == 204 || HTTPStatus::info?(@status)
137         @header.delete('content-length')
138         @body = ""
139       elsif chunked?
140         @header["transfer-encoding"] = "chunked"
141         @header.delete('content-length')
142       elsif %r{^multipart/byteranges} =~ @header['content-type']
143         @header.delete('content-length')
144       elsif @header['content-length'].nil?
145         unless @body.is_a?(IO)
146           @header['content-length'] = @body ? @body.size : 0
147         end
148       end
150       # Keep-Alive connection.
151       if @header['connection'] == "close"
152          @keep_alive = false
153       elsif keep_alive?
154         if chunked? || @header['content-length']
155           @header['connection'] = "Keep-Alive"
156         end
157       else
158         @header['connection'] = "close"
159       end
161       # Location is a single absoluteURI.
162       if location = @header['location']
163         if @request_uri
164           @header['location'] = @request_uri.merge(location)
165         end
166       end
167     end
169     def send_header(socket)
170       if @http_version.major > 0
171         data = status_line()
172         @header.each{|key, value|
173           tmp = key.gsub(/\bwww|^te$|\b\w/){|s| s.upcase }
174           data << "#{tmp}: #{value}" << CRLF
175         }
176         @cookies.each{|cookie|
177           data << "Set-Cookie: " << cookie.to_s << CRLF
178         }
179         data << CRLF
180         _write_data(socket, data)
181       end
182     end
184     def send_body(socket)
185       case @body
186       when IO then send_body_io(socket)
187       else send_body_string(socket)
188       end
189     end
191     def to_s
192       ret = ""
193       send_response(ret)
194       ret
195     end
197     def set_redirect(status, url)
198       @body = "<HTML><A HREF=\"#{url.to_s}\">#{url.to_s}</A>.</HTML>\n"
199       @header['location'] = url.to_s
200       raise status
201     end
203     def set_error(ex, backtrace=false)
204       case ex
205       when HTTPStatus::Status 
206         @keep_alive = false if HTTPStatus::error?(ex.code)
207         self.status = ex.code
208       else 
209         @keep_alive = false
210         self.status = HTTPStatus::RC_INTERNAL_SERVER_ERROR
211       end
212       @header['content-type'] = "text/html"
214       if respond_to?(:create_error_page)
215         create_error_page()
216         return
217       end
219       if @request_uri
220         host, port = @request_uri.host, @request_uri.port
221       else
222         host, port = @config[:ServerName], @config[:Port]
223       end
225       @body = ''
226       @body << <<-_end_of_html_
227 <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0//EN">
228 <HTML>
229   <HEAD><TITLE>#{HTMLUtils::escape(@reason_phrase)}</TITLE></HEAD>
230   <BODY>
231     <H1>#{HTMLUtils::escape(@reason_phrase)}</H1>
232     #{HTMLUtils::escape(ex.message)}
233     <HR>
234       _end_of_html_
236       if backtrace && $DEBUG
237         @body << "backtrace of `#{HTMLUtils::escape(ex.class.to_s)}' "
238         @body << "#{HTMLUtils::escape(ex.message)}"
239         @body << "<PRE>"
240         ex.backtrace.each{|line| @body << "\t#{line}\n"}
241         @body << "</PRE><HR>"
242       end
244       @body << <<-_end_of_html_
245     <ADDRESS>
246      #{HTMLUtils::escape(@config[:ServerSoftware])} at
247      #{host}:#{port}
248     </ADDRESS>
249   </BODY>
250 </HTML>
251       _end_of_html_
252     end
254     private
256     def send_body_io(socket)
257       begin
258         if @request_method == "HEAD"
259           # do nothing
260         elsif chunked?
261           while buf = @body.read(BUFSIZE)
262             next if buf.empty?
263             data = ""
264             data << format("%x", buf.size) << CRLF
265             data << buf << CRLF
266             _write_data(socket, data)
267             @sent_size += buf.size
268           end
269           _write_data(socket, "0#{CRLF}#{CRLF}")
270         else
271           size = @header['content-length'].to_i
272           _send_file(socket, @body, 0, size)
273           @sent_size = size
274         end
275       ensure
276         @body.close
277       end
278     end
280     def send_body_string(socket)
281       if @request_method == "HEAD"
282         # do nothing
283       elsif chunked?
284         remain = body ? @body.size : 0
285         while buf = @body[@sent_size, BUFSIZE]
286           break if buf.empty?
287           data = ""
288           data << format("%x", buf.size) << CRLF
289           data << buf << CRLF
290           _write_data(socket, data)
291           @sent_size += buf.size
292         end
293         _write_data(socket, "0#{CRLF}#{CRLF}")
294       else
295         if @body && @body.size > 0
296           _write_data(socket, @body)
297           @sent_size = @body.size
298         end
299       end
300     end
302     def _send_file(output, input, offset, size)
303       while offset > 0
304         sz = BUFSIZE < offset ? BUFSIZE : offset
305         buf = input.read(sz)
306         offset -= buf.size
307       end
309       if size == 0
310         while buf = input.read(BUFSIZE)
311           _write_data(output, buf)
312         end
313       else
314         while size > 0
315           sz = BUFSIZE < size ? BUFSIZE : size
316           buf = input.read(sz)
317           _write_data(output, buf)
318           size -= buf.size
319         end
320       end
321     end
323     def _write_data(socket, data)
324       socket << data
325     end
326   end