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
9 # $IPR: httpresponse.rb,v 1.45 2003/07/11 11:02:25 gotoyuzo Exp $
12 require 'webrick/httpversion'
13 require 'webrick/htmlutils'
14 require 'webrick/httputils'
15 require 'webrick/httpstatus'
21 attr_reader :http_version, :status, :header
23 attr_accessor :reason_phrase
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)
33 @logger = config[:Logger]
35 @status = HTTPStatus::RC_OK
37 @http_version = HTTPVersion::convert(@config[:HTTPVersion])
43 @request_http_version = @http_version # temporary
50 "HTTP/#@http_version #@status #@reason_phrase #{CRLF}"
55 @reason_phrase = HTTPStatus::reason_phrase(status)
59 @header[field.downcase]
63 @header[field.downcase] = value.to_s
67 if len = self['content-length']
72 def content_length=(len)
73 self['content-length'] = len.to_s
80 def content_type=(type)
81 self['content-type'] = type
85 @header.each{|k, v| yield(k, v) }
93 @chunked = val ? true : false
100 def send_response(socket)
105 rescue Errno::EPIPE, Errno::ECONNRESET, Errno::ENOTCONN => ex
108 rescue Exception => ex
115 @reason_phrase ||= HTTPStatus::reason_phrase(@status)
116 @header['server'] ||= @config[:ServerSoftware]
117 @header['date'] ||= Time.now.httpdate
120 if @request_http_version < "1.0"
121 @http_version = HTTPVersion.new("0.9")
126 if @request_http_version < "1.1"
129 ver = @request_http_version.to_s
130 msg = "chunked is set for an HTTP/#{ver} request. (ignored)"
135 # Determin the message length (RFC2616 -- 4.4 Message Length)
136 if @status == 304 || @status == 204 || HTTPStatus::info?(@status)
137 @header.delete('content-length')
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
150 # Keep-Alive connection.
151 if @header['connection'] == "close"
154 if chunked? || @header['content-length']
155 @header['connection'] = "Keep-Alive"
158 @header['connection'] = "close"
161 # Location is a single absoluteURI.
162 if location = @header['location']
164 @header['location'] = @request_uri.merge(location)
169 def send_header(socket)
170 if @http_version.major > 0
172 @header.each{|key, value|
173 tmp = key.gsub(/\bwww|^te$|\b\w/){|s| s.upcase }
174 data << "#{tmp}: #{value}" << CRLF
176 @cookies.each{|cookie|
177 data << "Set-Cookie: " << cookie.to_s << CRLF
180 _write_data(socket, data)
184 def send_body(socket)
186 when IO then send_body_io(socket)
187 else send_body_string(socket)
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
203 def set_error(ex, backtrace=false)
205 when HTTPStatus::Status
206 @keep_alive = false if HTTPStatus::error?(ex.code)
207 self.status = ex.code
210 self.status = HTTPStatus::RC_INTERNAL_SERVER_ERROR
212 @header['content-type'] = "text/html"
214 if respond_to?(:create_error_page)
220 host, port = @request_uri.host, @request_uri.port
222 host, port = @config[:ServerName], @config[:Port]
226 @body << <<-_end_of_html_
227 <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0//EN">
229 <HEAD><TITLE>#{HTMLUtils::escape(@reason_phrase)}</TITLE></HEAD>
231 <H1>#{HTMLUtils::escape(@reason_phrase)}</H1>
232 #{HTMLUtils::escape(ex.message)}
236 if backtrace && $DEBUG
237 @body << "backtrace of `#{HTMLUtils::escape(ex.class.to_s)}' "
238 @body << "#{HTMLUtils::escape(ex.message)}"
240 ex.backtrace.each{|line| @body << "\t#{line}\n"}
241 @body << "</PRE><HR>"
244 @body << <<-_end_of_html_
246 #{HTMLUtils::escape(@config[:ServerSoftware])} at
256 def send_body_io(socket)
258 if @request_method == "HEAD"
261 while buf = @body.read(BUFSIZE)
264 data << format("%x", buf.size) << CRLF
266 _write_data(socket, data)
267 @sent_size += buf.size
269 _write_data(socket, "0#{CRLF}#{CRLF}")
271 size = @header['content-length'].to_i
272 _send_file(socket, @body, 0, size)
280 def send_body_string(socket)
281 if @request_method == "HEAD"
284 remain = body ? @body.size : 0
285 while buf = @body[@sent_size, BUFSIZE]
288 data << format("%x", buf.size) << CRLF
290 _write_data(socket, data)
291 @sent_size += buf.size
293 _write_data(socket, "0#{CRLF}#{CRLF}")
295 if @body && @body.size > 0
296 _write_data(socket, @body)
297 @sent_size = @body.size
302 def _send_file(output, input, offset, size)
304 sz = BUFSIZE < offset ? BUFSIZE : offset
310 while buf = input.read(BUFSIZE)
311 _write_data(output, buf)
315 sz = BUFSIZE < size ? BUFSIZE : size
317 _write_data(output, buf)
323 def _write_data(socket, data)