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'
19 attr_reader :http_version, :status, :header
21 attr_accessor :reason_phrase
24 attr_accessor :request_method, :request_uri, :request_http_version
25 attr_accessor :filename
26 attr_accessor :keep_alive
27 attr_reader :config, :sent_size
29 def initialize(config)
31 @buffer_size = config[:OutputBufferSize]
32 @logger = config[:Logger]
34 @status = HTTPStatus::RC_OK
36 @http_version = HTTPVersion::convert(@config[:HTTPVersion])
42 @request_http_version = @http_version # temporary
49 "HTTP/#@http_version #@status #@reason_phrase #{CRLF}"
54 @reason_phrase = HTTPStatus::reason_phrase(status)
58 @header[field.downcase]
62 @header[field.downcase] = value.to_s
66 if len = self['content-length']
71 def content_length=(len)
72 self['content-length'] = len.to_s
79 def content_type=(type)
80 self['content-type'] = type
84 @header.each{|k, v| yield(k, v) }
92 @chunked = val ? true : false
99 def send_response(socket)
104 rescue Errno::EPIPE, Errno::ECONNRESET, Errno::ENOTCONN => ex
107 rescue Exception => ex
114 @reason_phrase ||= HTTPStatus::reason_phrase(@status)
115 @header['server'] ||= @config[:ServerSoftware]
116 @header['date'] ||= Time.now.httpdate
119 if @request_http_version < "1.0"
120 @http_version = HTTPVersion.new("0.9")
125 if @request_http_version < "1.1"
128 ver = @request_http_version.to_s
129 msg = "chunked is set for an HTTP/#{ver} request. (ignored)"
134 # Determine the message length (RFC2616 -- 4.4 Message Length)
135 if @status == 304 || @status == 204 || HTTPStatus::info?(@status)
136 @header.delete('content-length')
139 @header["transfer-encoding"] = "chunked"
140 @header.delete('content-length')
141 elsif %r{^multipart/byteranges} =~ @header['content-type']
142 @header.delete('content-length')
143 elsif @header['content-length'].nil?
144 unless @body.is_a?(IO)
145 @header['content-length'] = @body ? @body.bytesize : 0
149 # Keep-Alive connection.
150 if @header['connection'] == "close"
153 if chunked? || @header['content-length']
154 @header['connection'] = "Keep-Alive"
157 @header['connection'] = "close"
160 # Location is a single absoluteURI.
161 if location = @header['location']
163 @header['location'] = @request_uri.merge(location)
168 def send_header(socket)
169 if @http_version.major > 0
171 @header.each{|key, value|
172 tmp = key.gsub(/\bwww|^te$|\b\w/){ $&.upcase }
173 data << "#{tmp}: #{value}" << CRLF
175 @cookies.each{|cookie|
176 data << "Set-Cookie: " << cookie.to_s << CRLF
179 _write_data(socket, data)
183 def send_body(socket)
185 when IO then send_body_io(socket)
186 else send_body_string(socket)
196 def set_redirect(status, url)
197 @body = "<HTML><A HREF=\"#{url.to_s}\">#{url.to_s}</A>.</HTML>\n"
198 @header['location'] = url.to_s
202 def set_error(ex, backtrace=false)
204 when HTTPStatus::Status
205 @keep_alive = false if HTTPStatus::error?(ex.code)
206 self.status = ex.code
209 self.status = HTTPStatus::RC_INTERNAL_SERVER_ERROR
211 @header['content-type'] = "text/html"
213 if respond_to?(:create_error_page)
219 host, port = @request_uri.host, @request_uri.port
221 host, port = @config[:ServerName], @config[:Port]
225 @body << <<-_end_of_html_
226 <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0//EN">
228 <HEAD><TITLE>#{HTMLUtils::escape(@reason_phrase)}</TITLE></HEAD>
230 <H1>#{HTMLUtils::escape(@reason_phrase)}</H1>
231 #{HTMLUtils::escape(ex.message)}
235 if backtrace && $DEBUG
236 @body << "backtrace of `#{HTMLUtils::escape(ex.class.to_s)}' "
237 @body << "#{HTMLUtils::escape(ex.message)}"
239 ex.backtrace.each{|line| @body << "\t#{line}\n"}
240 @body << "</PRE><HR>"
243 @body << <<-_end_of_html_
245 #{HTMLUtils::escape(@config[:ServerSoftware])} at
255 def send_body_io(socket)
257 if @request_method == "HEAD"
260 while buf = @body.read(@buffer_size)
263 data << format("%x", buf.bytesize) << CRLF
265 _write_data(socket, data)
266 @sent_size += buf.bytesize
268 _write_data(socket, "0#{CRLF}#{CRLF}")
270 size = @header['content-length'].to_i
271 _send_file(socket, @body, 0, size)
279 def send_body_string(socket)
280 if @request_method == "HEAD"
283 remain = body ? @body.bytesize : 0
284 while buf = @body[@sent_size, @buffer_size]
287 data << format("%x", buf.bytesize) << CRLF
289 _write_data(socket, data)
290 @sent_size += buf.bytesize
292 _write_data(socket, "0#{CRLF}#{CRLF}")
294 if @body && @body.bytesize > 0
295 _write_data(socket, @body)
296 @sent_size = @body.bytesize
301 def _send_file(output, input, offset, size)
303 sz = @buffer_size < size ? @buffer_size : size
305 offset -= buf.bytesize
309 while buf = input.read(@buffer_size)
310 _write_data(output, buf)
314 sz = @buffer_size < size ? @buffer_size : size
316 _write_data(output, buf)
322 def _write_data(socket, data)