diff options
author | Jari Vetoniemi <jari.vetoniemi@indooratlas.com> | 2020-03-16 18:49:26 +0900 |
---|---|---|
committer | Jari Vetoniemi <jari.vetoniemi@indooratlas.com> | 2020-03-30 00:39:06 +0900 |
commit | fcbf63e62c627deae76c1b8cb8c0876c536ed811 (patch) | |
tree | 64cb17de3f41a2b6fef2368028fbd00349946994 /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.rb | 466 |
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 |