summaryrefslogtreecommitdiff
path: root/jni/ruby/lib/webrick/httpresponse.rb
diff options
context:
space:
mode:
authorJari Vetoniemi <jari.vetoniemi@indooratlas.com>2020-03-16 18:49:26 +0900
committerJari Vetoniemi <jari.vetoniemi@indooratlas.com>2020-03-30 00:39:06 +0900
commitfcbf63e62c627deae76c1b8cb8c0876c536ed811 (patch)
tree64cb17de3f41a2b6fef2368028fbd00349946994 /jni/ruby/lib/webrick/httpresponse.rb
Fresh start
Diffstat (limited to 'jni/ruby/lib/webrick/httpresponse.rb')
-rw-r--r--jni/ruby/lib/webrick/httpresponse.rb466
1 files changed, 466 insertions, 0 deletions
diff --git a/jni/ruby/lib/webrick/httpresponse.rb b/jni/ruby/lib/webrick/httpresponse.rb
new file mode 100644
index 0000000..e897e8c
--- /dev/null
+++ b/jni/ruby/lib/webrick/httpresponse.rb
@@ -0,0 +1,466 @@
+#
+# 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
+ ##
+ # An HTTP response. This is filled in by the service or do_* methods of a
+ # WEBrick HTTP Servlet.
+
+ class HTTPResponse
+
+ ##
+ # HTTP Response version
+
+ attr_reader :http_version
+
+ ##
+ # Response status code (200)
+
+ attr_reader :status
+
+ ##
+ # Response header
+
+ attr_reader :header
+
+ ##
+ # Response cookies
+
+ attr_reader :cookies
+
+ ##
+ # Response reason phrase ("OK")
+
+ attr_accessor :reason_phrase
+
+ ##
+ # Body may be a String or IO-like object that responds to #read and
+ # #readpartial.
+
+ attr_accessor :body
+
+ ##
+ # Request method for this response
+
+ attr_accessor :request_method
+
+ ##
+ # Request URI for this response
+
+ attr_accessor :request_uri
+
+ ##
+ # Request HTTP version for this response
+
+ attr_accessor :request_http_version
+
+ ##
+ # Filename of the static file in this response. Only used by the
+ # FileHandler servlet.
+
+ attr_accessor :filename
+
+ ##
+ # Is this a keep-alive response?
+
+ attr_accessor :keep_alive
+
+ ##
+ # Configuration for this response
+
+ attr_reader :config
+
+ ##
+ # Bytes sent in this response
+
+ attr_reader :sent_size
+
+ ##
+ # Creates a new HTTP response object. WEBrick::Config::HTTP is the
+ # default configuration.
+
+ def initialize(config)
+ @config = config
+ @buffer_size = config[:OutputBufferSize]
+ @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
+
+ ##
+ # The response's HTTP status line
+
+ def status_line
+ "HTTP/#@http_version #@status #@reason_phrase #{CRLF}"
+ end
+
+ ##
+ # Sets the response's status to the +status+ code
+
+ def status=(status)
+ @status = status
+ @reason_phrase = HTTPStatus::reason_phrase(status)
+ end
+
+ ##
+ # Retrieves the response header +field+
+
+ def [](field)
+ @header[field.downcase]
+ end
+
+ ##
+ # Sets the response header +field+ to +value+
+
+ def []=(field, value)
+ @header[field.downcase] = value.to_s
+ end
+
+ ##
+ # The content-length header
+
+ def content_length
+ if len = self['content-length']
+ return Integer(len)
+ end
+ end
+
+ ##
+ # Sets the content-length header to +len+
+
+ def content_length=(len)
+ self['content-length'] = len.to_s
+ end
+
+ ##
+ # The content-type header
+
+ def content_type
+ self['content-type']
+ end
+
+ ##
+ # Sets the content-type header to +type+
+
+ def content_type=(type)
+ self['content-type'] = type
+ end
+
+ ##
+ # Iterates over each header in the response
+
+ def each
+ @header.each{|field, value| yield(field, value) }
+ end
+
+ ##
+ # Will this response body be returned using chunked transfer-encoding?
+
+ def chunked?
+ @chunked
+ end
+
+ ##
+ # Enables chunked transfer encoding.
+
+ def chunked=(val)
+ @chunked = val ? true : false
+ end
+
+ ##
+ # Will this response's connection be kept alive?
+
+ def keep_alive?
+ @keep_alive
+ end
+
+ ##
+ # Sends the response on +socket+
+
+ def send_response(socket) # :nodoc:
+ begin
+ setup_header()
+ send_header(socket)
+ send_body(socket)
+ rescue Errno::EPIPE, Errno::ECONNRESET, Errno::ENOTCONN => ex
+ @logger.debug(ex)
+ @keep_alive = false
+ rescue Exception => ex
+ @logger.error(ex)
+ @keep_alive = false
+ end
+ end
+
+ ##
+ # Sets up the headers for sending
+
+ def setup_header() # :nodoc:
+ @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
+
+ # Determine 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.bytesize : 0
+ end
+ end
+
+ # Keep-Alive connection.
+ if @header['connection'] == "close"
+ @keep_alive = false
+ elsif keep_alive?
+ if chunked? || @header['content-length'] || @status == 304 || @status == 204 || HTTPStatus.info?(@status)
+ @header['connection'] = "Keep-Alive"
+ else
+ msg = "Could not determine content-length of response body. Set content-length of the response or set Response#chunked = true"
+ @logger.warn(msg)
+ @header['connection'] = "close"
+ @keep_alive = false
+ end
+ else
+ @header['connection'] = "close"
+ end
+
+ # Location is a single absoluteURI.
+ if location = @header['location']
+ if @request_uri
+ @header['location'] = @request_uri.merge(location)
+ end
+ end
+ end
+
+ ##
+ # Sends the headers on +socket+
+
+ def send_header(socket) # :nodoc:
+ if @http_version.major > 0
+ data = status_line()
+ @header.each{|key, value|
+ tmp = key.gsub(/\bwww|^te$|\b\w/){ $&.upcase }
+ data << "#{tmp}: #{value}" << CRLF
+ }
+ @cookies.each{|cookie|
+ data << "Set-Cookie: " << cookie.to_s << CRLF
+ }
+ data << CRLF
+ _write_data(socket, data)
+ end
+ end
+
+ ##
+ # Sends the body on +socket+
+
+ def send_body(socket) # :nodoc:
+ if @body.respond_to? :readpartial then
+ send_body_io(socket)
+ else
+ send_body_string(socket)
+ end
+ end
+
+ def to_s # :nodoc:
+ ret = ""
+ send_response(ret)
+ ret
+ end
+
+ ##
+ # Redirects to +url+ with a WEBrick::HTTPStatus::Redirect +status+.
+ #
+ # Example:
+ #
+ # res.set_redirect WEBrick::HTTPStatus::TemporaryRedirect
+
+ def set_redirect(status, url)
+ @body = "<HTML><A HREF=\"#{url}\">#{url}</A>.</HTML>\n"
+ @header['location'] = url.to_s
+ raise status
+ end
+
+ ##
+ # Creates an error page for exception +ex+ with an optional +backtrace+
+
+ 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; charset=ISO-8859-1"
+
+ 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_
+<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0//EN">
+<HTML>
+ <HEAD><TITLE>#{HTMLUtils::escape(@reason_phrase)}</TITLE></HEAD>
+ <BODY>
+ <H1>#{HTMLUtils::escape(@reason_phrase)}</H1>
+ #{HTMLUtils::escape(ex.message)}
+ <HR>
+ _end_of_html_
+
+ if backtrace && $DEBUG
+ @body << "backtrace of `#{HTMLUtils::escape(ex.class.to_s)}' "
+ @body << "#{HTMLUtils::escape(ex.message)}"
+ @body << "<PRE>"
+ ex.backtrace.each{|line| @body << "\t#{line}\n"}
+ @body << "</PRE><HR>"
+ end
+
+ @body << <<-_end_of_html_
+ <ADDRESS>
+ #{HTMLUtils::escape(@config[:ServerSoftware])} at
+ #{host}:#{port}
+ </ADDRESS>
+ </BODY>
+</HTML>
+ _end_of_html_
+ end
+
+ private
+
+ # :stopdoc:
+
+ def send_body_io(socket)
+ begin
+ if @request_method == "HEAD"
+ # do nothing
+ elsif chunked?
+ begin
+ buf = ''
+ data = ''
+ while true
+ @body.readpartial( @buffer_size, buf ) # there is no need to clear buf?
+ data << format("%x", buf.bytesize) << CRLF
+ data << buf << CRLF
+ _write_data(socket, data)
+ data.clear
+ @sent_size += buf.bytesize
+ end
+ rescue EOFError # do nothing
+ end
+ _write_data(socket, "0#{CRLF}#{CRLF}")
+ else
+ size = @header['content-length'].to_i
+ _send_file(socket, @body, 0, size)
+ @sent_size = size
+ end
+ ensure
+ @body.close
+ end
+ end
+
+ def send_body_string(socket)
+ if @request_method == "HEAD"
+ # do nothing
+ elsif chunked?
+ body ? @body.bytesize : 0
+ while buf = @body[@sent_size, @buffer_size]
+ break if buf.empty?
+ data = ""
+ data << format("%x", buf.bytesize) << CRLF
+ data << buf << CRLF
+ _write_data(socket, data)
+ @sent_size += buf.bytesize
+ end
+ _write_data(socket, "0#{CRLF}#{CRLF}")
+ else
+ if @body && @body.bytesize > 0
+ _write_data(socket, @body)
+ @sent_size = @body.bytesize
+ end
+ end
+ end
+
+ def _send_file(output, input, offset, size)
+ while offset > 0
+ sz = @buffer_size < size ? @buffer_size : size
+ buf = input.read(sz)
+ offset -= buf.bytesize
+ end
+
+ if size == 0
+ while buf = input.read(@buffer_size)
+ _write_data(output, buf)
+ end
+ else
+ while size > 0
+ sz = @buffer_size < size ? @buffer_size : size
+ buf = input.read(sz)
+ _write_data(output, buf)
+ size -= buf.bytesize
+ end
+ end
+ end
+
+ def _write_data(socket, data)
+ socket << data
+ end
+
+ # :startdoc:
+ end
+
+end