From 01eba908adcd150a7b816af0dbe167c4c4912a90 Mon Sep 17 00:00:00 2001 From: gotoyuzo Date: Wed, 23 Jul 2003 16:51:36 +0000 Subject: * lib/webrick: imported. git-svn-id: svn+ssh://ci.ruby-lang.org/ruby/trunk@4130 b2dd03c8-39d4-4d8f-98ff-823fe69b080e --- lib/webrick/httpresponse.rb | 304 ++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 304 insertions(+) create mode 100644 lib/webrick/httpresponse.rb (limited to 'lib/webrick/httpresponse.rb') diff --git a/lib/webrick/httpresponse.rb b/lib/webrick/httpresponse.rb new file mode 100644 index 0000000000..6b00c2b88b --- /dev/null +++ b/lib/webrick/httpresponse.rb @@ -0,0 +1,304 @@ +# +# httpresponse.rb -- HTTPResponse Class +# +# Author: IPR -- Internet Programming with Ruby -- writers +# Copyright (c) 2000, 2001 TAKAHASHI Masayoshi, GOTOU Yuuzou +# Copyright (c) 2002 Internet Programming with Ruby writers. All rights +# reserved. +# +# $IPR: httpresponse.rb,v 1.45 2003/07/11 11:02:25 gotoyuzo Exp $ + +require 'time' +require 'webrick/httpversion' +require 'webrick/htmlutils' +require 'webrick/httputils' +require 'webrick/httpstatus' + +module WEBrick + class HTTPResponse + BUFSIZE = 1024*4 + + attr_reader :http_version, :status, :header + attr_reader :cookies + attr_accessor :reason_phrase + attr_accessor :body + + attr_accessor :request_method, :request_uri, :request_http_version + attr_accessor :filename + attr_reader :config, :keep_alive, :sent_size + + def initialize(config) + @config = config + @logger = config[:Logger] + @header = Hash.new + @status = HTTPStatus::RC_OK + @reason_phrase = nil + @http_version = HTTPVersion::convert(@config[:HTTPVersion]) + @body = '' + @keep_alive = true + @cookies = [] + @request_method = nil + @request_uri = nil + @request_http_version = @http_version # temporary + @chunked = false + @filename = nil + @sent_size = 0 + end + + def status_line + "HTTP/#@http_version #@status #@reason_phrase #{CRLF}" + end + + def status=(status) + @status = status + @reason_phrase = HTTPStatus::reason_phrase(status) + end + + def [](field) + @header[field.downcase] + end + + def []=(field, value) + @header[field.downcase] = value.to_s + end + + def each + @header.each{|k, v| yield(k, v) } + end + + def chunked? + @chunked + end + + def chunked=(val) + @chunked = val ? true : false + end + + def keep_alive? + @keep_alive + end + + def send_response(socket) + begin + setup_header() + send_header(socket) + send_body(socket) + rescue Errno::EPIPE + @logger.error("HTTPResponse#send_response: EPIPE occured.") + @keep_alive = false + rescue => ex + @logger.error(ex) + @keep_alive = false + end + end + + def setup_header() + @reason_phrase ||= HTTPStatus::reason_phrase(@status) + @header['server'] ||= @config[:ServerSoftware] + @header['date'] ||= Time.now.httpdate + + # HTTP/0.9 features + if @request_http_version < "1.0" + @http_version = HTTPVersion.new("0.9") + @keep_alive = false + end + + # HTTP/1.0 features + if @request_http_version < "1.1" + if chunked? + @chunked = false + ver = @request_http_version.to_s + msg = "chunked is set for an HTTP/#{ver} request. (ignored)" + @logger.warn(msg) + end + end + + # Determin the message length (RFC2616 -- 4.4 Message Length) + if @status == 304 || @status == 204 || HTTPStatus::info?(@status) + @header.delete('content-length') + @body = "" + elsif chunked? + @header["transfer-encoding"] = "chunked" + @header.delete('content-length') + elsif %r{^multipart/byteranges} =~ @header['content-type'] + @header.delete('content-length') + elsif @header['content-length'].nil? + unless @body.is_a?(IO) + @header['content-length'] = @body ? @body.size : 0 + end + end + + # Keep-Alive connection. + if @header['connection'] == "close" + @keep_alive = false + end + if keep_alive? + if chunked? || @header['content-length'] + @header['connection'] = "Keep-Alive" + end + end + + # Location is a single absoluteURI. + if location = @header['location'] + if @request_uri + @header['location'] = @request_uri.merge(location) + end + end + end + + def send_header(socket) + if @http_version.major > 0 + data = status_line() + @header.each{|key, value| + tmp = key.gsub(/\bwww|^te$|\b\w/){|s| s.upcase } + data << "#{tmp}: #{value}" << CRLF + } + @cookies.each{|cookie| + data << "Set-Cookie: " << cookie.to_s << CRLF + } + data << CRLF + _write_data(socket, data) + end + end + + def send_body(socket) + case @body + when IO then send_body_io(socket) + else send_body_string(socket) + end + end + + def to_s + ret = "" + send_response(ret) + ret + end + + def set_redirect(status, url) + @body = "#{url.to_s}.\n" + @header['location'] = url.to_s + raise status + end + + def set_error(ex, backtrace=false) + case ex + when HTTPStatus::Status + @keep_alive = false if HTTPStatus::error?(ex.code) + self.status = ex.code + else + @keep_alive = false + self.status = HTTPStatus::RC_INTERNAL_SERVER_ERROR + end + @header['content-type'] = "text/html" + + if respond_to?(:create_error_page) + create_error_page() + return + end + + if @request_uri + host, port = @request_uri.host, @request_uri.port + else + host, port = @config[:ServerName], @config[:Port] + end + + @body = '' + @body << <<-_end_of_html_ + + + #{HTMLUtils::escape(@reason_phrase)} + +

#{HTMLUtils::escape(@reason_phrase)}

+ #{HTMLUtils::escape(ex.message)} +
+ _end_of_html_ + + if backtrace && $DEBUG + @body << "backtrace of `#{HTMLUtils::escape(ex.class.to_s)}' " + @body << "#{HTMLUtils::escape(ex.message)}" + @body << "
"
+        ex.backtrace.each{|line| @body << "\t#{line}\n"}
+        @body << "

" + end + + @body << <<-_end_of_html_ +
+ #{HTMLUtils::escape(@config[:ServerSoftware])} at + #{host}:#{port} +
+ + + _end_of_html_ + end + + private + + def send_body_io(socket) + if @request_method == "HEAD" + # do nothing + elsif chunked? + while buf = @body.read(BUFSIZE) + next if buf.empty? + data = "" + data << format("%x", buf.size) << CRLF + data << buf << CRLF + _write_data(socket, data) + @sent_size += buf.size + end + _write_data(socket, "0#{CRLF}#{CRLF}") + else + size = @header['content-length'].to_i + _send_file(socket, @body, 0, size.to_i) + @sent_size = size + end + @body.close + end + + def send_body_string(socket) + if @request_method == "HEAD" + # do nothing + elsif chunked? + remain = body ? @body.size : 0 + while buf = @body[@sent_size, BUFSIZE] + break if buf.empty? + data = "" + data << format("%x", buf.size) << CRLF + data << buf << CRLF + _write_data(socket, data) + @sent_size += buf.size + end + _write_data(socket, "0#{CRLF}#{CRLF}") + else + if @body && @body.size > 0 + _write_data(socket, @body) + @sent_size = @body.size + end + end + end + + def _send_file(output, input, offset, size) + while offset > 0 + sz = BUFSIZE < offset ? BUFSIZE : offset + buf = input.read(sz) + offset -= buf.size + end + + if size == 0 + while buf = input.read(BUFSIZE) + _write_data(output, buf) + end + else + while size > 0 + sz = BUFSIZE < size ? BUFSIZE : size + buf = input.read(sz) + _write_data(output, buf) + size -= buf.size + end + end + end + + def _write_data(socket, data) + socket << data + end + end +end -- cgit v1.2.3