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)