2015-12-16 00:07:31 -05:00
|
|
|
# frozen_string_literal: false
|
2003-07-23 12:51:36 -04:00
|
|
|
#
|
|
|
|
# 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'
|
2018-07-13 22:59:39 -04:00
|
|
|
require 'uri'
|
2018-11-02 13:52:33 -04:00
|
|
|
require_relative 'httpversion'
|
|
|
|
require_relative 'htmlutils'
|
|
|
|
require_relative 'httputils'
|
|
|
|
require_relative 'httpstatus'
|
2003-07-23 12:51:36 -04:00
|
|
|
|
|
|
|
module WEBrick
|
2011-05-09 20:13:58 -04:00
|
|
|
##
|
2013-01-25 20:12:54 -05:00
|
|
|
# An HTTP response. This is filled in by the service or do_* methods of a
|
|
|
|
# WEBrick HTTP Servlet.
|
2011-05-09 20:13:58 -04:00
|
|
|
|
2003-07-23 12:51:36 -04:00
|
|
|
class HTTPResponse
|
2018-03-28 04:38:26 -04:00
|
|
|
class InvalidHeader < StandardError
|
|
|
|
end
|
2013-01-25 20:12:54 -05:00
|
|
|
|
|
|
|
##
|
|
|
|
# HTTP Response version
|
|
|
|
|
|
|
|
attr_reader :http_version
|
|
|
|
|
|
|
|
##
|
|
|
|
# Response status code (200)
|
|
|
|
|
|
|
|
attr_reader :status
|
|
|
|
|
|
|
|
##
|
|
|
|
# Response header
|
|
|
|
|
|
|
|
attr_reader :header
|
|
|
|
|
|
|
|
##
|
|
|
|
# Response cookies
|
|
|
|
|
2003-07-23 12:51:36 -04:00
|
|
|
attr_reader :cookies
|
2013-01-25 20:12:54 -05:00
|
|
|
|
|
|
|
##
|
|
|
|
# Response reason phrase ("OK")
|
|
|
|
|
2003-07-23 12:51:36 -04:00
|
|
|
attr_accessor :reason_phrase
|
2011-05-09 20:13:58 -04:00
|
|
|
|
|
|
|
##
|
2019-10-26 06:27:17 -04:00
|
|
|
# Body may be:
|
|
|
|
# * a String;
|
|
|
|
# * an IO-like object that responds to +#read+ and +#readpartial+;
|
|
|
|
# * a Proc-like object that responds to +#call+.
|
|
|
|
#
|
|
|
|
# In the latter case, either #chunked= should be set to +true+,
|
|
|
|
# or <code>header['content-length']</code> explicitly provided.
|
|
|
|
# Example:
|
|
|
|
#
|
|
|
|
# server.mount_proc '/' do |req, res|
|
|
|
|
# res.chunked = true
|
|
|
|
# # or
|
|
|
|
# # res.header['content-length'] = 10
|
|
|
|
# res.body = proc { |out| out.write(Time.now.to_s) }
|
|
|
|
# end
|
2011-05-09 20:13:58 -04:00
|
|
|
|
2003-07-23 12:51:36 -04:00
|
|
|
attr_accessor :body
|
|
|
|
|
2013-01-25 20:12:54 -05:00
|
|
|
##
|
|
|
|
# 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.
|
|
|
|
|
2003-07-23 12:51:36 -04:00
|
|
|
attr_accessor :filename
|
2013-01-25 20:12:54 -05:00
|
|
|
|
|
|
|
##
|
|
|
|
# Is this a keep-alive response?
|
|
|
|
|
2003-11-25 11:02:45 -05:00
|
|
|
attr_accessor :keep_alive
|
2003-07-23 12:51:36 -04:00
|
|
|
|
2011-05-09 20:13:58 -04:00
|
|
|
##
|
2013-01-25 20:12:54 -05:00
|
|
|
# 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.
|
2011-05-09 20:13:58 -04:00
|
|
|
|
2003-07-23 12:51:36 -04:00
|
|
|
def initialize(config)
|
|
|
|
@config = config
|
2006-05-18 09:42:52 -04:00
|
|
|
@buffer_size = config[:OutputBufferSize]
|
2003-07-23 12:51:36 -04:00
|
|
|
@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
|
2019-07-10 20:18:41 -04:00
|
|
|
@bodytempfile = nil
|
2003-07-23 12:51:36 -04:00
|
|
|
end
|
|
|
|
|
2011-05-09 20:13:58 -04:00
|
|
|
##
|
|
|
|
# The response's HTTP status line
|
|
|
|
|
2003-07-23 12:51:36 -04:00
|
|
|
def status_line
|
2019-03-09 05:53:51 -05:00
|
|
|
"HTTP/#@http_version #@status #@reason_phrase".rstrip << CRLF
|
2003-07-23 12:51:36 -04:00
|
|
|
end
|
|
|
|
|
2011-05-09 20:13:58 -04:00
|
|
|
##
|
|
|
|
# Sets the response's status to the +status+ code
|
|
|
|
|
2003-07-23 12:51:36 -04:00
|
|
|
def status=(status)
|
|
|
|
@status = status
|
|
|
|
@reason_phrase = HTTPStatus::reason_phrase(status)
|
|
|
|
end
|
|
|
|
|
2011-05-09 20:13:58 -04:00
|
|
|
##
|
|
|
|
# Retrieves the response header +field+
|
|
|
|
|
2003-07-23 12:51:36 -04:00
|
|
|
def [](field)
|
|
|
|
@header[field.downcase]
|
|
|
|
end
|
|
|
|
|
2011-05-09 20:13:58 -04:00
|
|
|
##
|
|
|
|
# Sets the response header +field+ to +value+
|
|
|
|
|
2003-07-23 12:51:36 -04:00
|
|
|
def []=(field, value)
|
2019-08-11 15:57:11 -04:00
|
|
|
@chunked = value.to_s.downcase == 'chunked' if field.downcase == 'transfer-encoding'
|
2003-07-23 12:51:36 -04:00
|
|
|
@header[field.downcase] = value.to_s
|
|
|
|
end
|
|
|
|
|
2011-05-09 20:13:58 -04:00
|
|
|
##
|
|
|
|
# The content-length header
|
|
|
|
|
2004-10-12 08:26:39 -04:00
|
|
|
def content_length
|
|
|
|
if len = self['content-length']
|
|
|
|
return Integer(len)
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
2011-05-09 20:13:58 -04:00
|
|
|
##
|
|
|
|
# Sets the content-length header to +len+
|
|
|
|
|
2004-10-12 08:26:39 -04:00
|
|
|
def content_length=(len)
|
|
|
|
self['content-length'] = len.to_s
|
|
|
|
end
|
|
|
|
|
2011-05-09 20:13:58 -04:00
|
|
|
##
|
|
|
|
# The content-type header
|
|
|
|
|
2004-10-12 08:26:39 -04:00
|
|
|
def content_type
|
|
|
|
self['content-type']
|
|
|
|
end
|
|
|
|
|
2011-05-09 20:13:58 -04:00
|
|
|
##
|
|
|
|
# Sets the content-type header to +type+
|
|
|
|
|
2004-10-12 08:26:39 -04:00
|
|
|
def content_type=(type)
|
|
|
|
self['content-type'] = type
|
|
|
|
end
|
|
|
|
|
2011-05-09 20:13:58 -04:00
|
|
|
##
|
2013-12-05 06:32:26 -05:00
|
|
|
# Iterates over each header in the response
|
2011-05-09 20:13:58 -04:00
|
|
|
|
2003-07-23 12:51:36 -04:00
|
|
|
def each
|
2011-05-09 20:13:58 -04:00
|
|
|
@header.each{|field, value| yield(field, value) }
|
2003-07-23 12:51:36 -04:00
|
|
|
end
|
|
|
|
|
2011-05-09 20:13:58 -04:00
|
|
|
##
|
|
|
|
# Will this response body be returned using chunked transfer-encoding?
|
|
|
|
|
2003-07-23 12:51:36 -04:00
|
|
|
def chunked?
|
|
|
|
@chunked
|
|
|
|
end
|
|
|
|
|
2011-05-09 20:13:58 -04:00
|
|
|
##
|
|
|
|
# Enables chunked transfer encoding.
|
|
|
|
|
2003-07-23 12:51:36 -04:00
|
|
|
def chunked=(val)
|
|
|
|
@chunked = val ? true : false
|
|
|
|
end
|
|
|
|
|
2011-05-09 20:13:58 -04:00
|
|
|
##
|
|
|
|
# Will this response's connection be kept alive?
|
|
|
|
|
2003-07-23 12:51:36 -04:00
|
|
|
def keep_alive?
|
|
|
|
@keep_alive
|
|
|
|
end
|
|
|
|
|
2011-05-09 20:13:58 -04:00
|
|
|
##
|
|
|
|
# Sends the response on +socket+
|
|
|
|
|
2013-01-25 20:12:54 -05:00
|
|
|
def send_response(socket) # :nodoc:
|
2003-07-23 12:51:36 -04:00
|
|
|
begin
|
|
|
|
setup_header()
|
|
|
|
send_header(socket)
|
|
|
|
send_body(socket)
|
2003-12-03 19:12:14 -05:00
|
|
|
rescue Errno::EPIPE, Errno::ECONNRESET, Errno::ENOTCONN => ex
|
|
|
|
@logger.debug(ex)
|
2003-07-23 12:51:36 -04:00
|
|
|
@keep_alive = false
|
2003-12-03 19:12:14 -05:00
|
|
|
rescue Exception => ex
|
2003-07-23 12:51:36 -04:00
|
|
|
@logger.error(ex)
|
|
|
|
@keep_alive = false
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
2011-05-09 20:13:58 -04:00
|
|
|
##
|
|
|
|
# Sets up the headers for sending
|
|
|
|
|
2013-01-25 20:12:54 -05:00
|
|
|
def setup_header() # :nodoc:
|
2003-07-23 12:51:36 -04:00
|
|
|
@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
|
|
|
|
|
2008-06-04 05:37:38 -04:00
|
|
|
# Determine the message length (RFC2616 -- 4.4 Message Length)
|
2003-07-23 12:51:36 -04:00
|
|
|
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?
|
2019-07-10 20:18:41 -04:00
|
|
|
if @body.respond_to? :readpartial
|
|
|
|
elsif @body.respond_to? :call
|
|
|
|
make_body_tempfile
|
|
|
|
else
|
2018-11-26 00:29:45 -05:00
|
|
|
@header['content-length'] = (@body ? @body.bytesize : 0).to_s
|
2003-07-23 12:51:36 -04:00
|
|
|
end
|
|
|
|
end
|
|
|
|
|
|
|
|
# Keep-Alive connection.
|
|
|
|
if @header['connection'] == "close"
|
|
|
|
@keep_alive = false
|
2003-12-22 16:13:06 -05:00
|
|
|
elsif keep_alive?
|
2011-12-12 17:33:56 -05:00
|
|
|
if chunked? || @header['content-length'] || @status == 304 || @status == 204 || HTTPStatus.info?(@status)
|
2003-07-23 12:51:36 -04:00
|
|
|
@header['connection'] = "Keep-Alive"
|
2011-06-21 08:58:37 -04:00
|
|
|
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
|
2003-07-23 12:51:36 -04:00
|
|
|
end
|
2003-12-22 16:13:06 -05:00
|
|
|
else
|
|
|
|
@header['connection'] = "close"
|
2003-07-23 12:51:36 -04:00
|
|
|
end
|
|
|
|
|
|
|
|
# Location is a single absoluteURI.
|
|
|
|
if location = @header['location']
|
|
|
|
if @request_uri
|
2018-11-26 00:29:45 -05:00
|
|
|
@header['location'] = @request_uri.merge(location).to_s
|
2003-07-23 12:51:36 -04:00
|
|
|
end
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
2019-07-10 20:18:41 -04:00
|
|
|
def make_body_tempfile # :nodoc:
|
|
|
|
return if @bodytempfile
|
|
|
|
bodytempfile = Tempfile.create("webrick")
|
|
|
|
if @body.nil?
|
|
|
|
# nothing
|
|
|
|
elsif @body.respond_to? :readpartial
|
|
|
|
IO.copy_stream(@body, bodytempfile)
|
|
|
|
@body.close
|
|
|
|
elsif @body.respond_to? :call
|
|
|
|
@body.call(bodytempfile)
|
|
|
|
else
|
|
|
|
bodytempfile.write @body
|
|
|
|
end
|
|
|
|
bodytempfile.rewind
|
|
|
|
@body = @bodytempfile = bodytempfile
|
|
|
|
@header['content-length'] = bodytempfile.stat.size.to_s
|
|
|
|
end
|
|
|
|
|
|
|
|
def remove_body_tempfile # :nodoc:
|
|
|
|
if @bodytempfile
|
|
|
|
@bodytempfile.close
|
|
|
|
File.unlink @bodytempfile.path
|
|
|
|
@bodytempfile = nil
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
|
|
|
|
2011-05-09 20:13:58 -04:00
|
|
|
##
|
|
|
|
# Sends the headers on +socket+
|
|
|
|
|
2013-01-25 20:12:54 -05:00
|
|
|
def send_header(socket) # :nodoc:
|
2003-07-23 12:51:36 -04:00
|
|
|
if @http_version.major > 0
|
|
|
|
data = status_line()
|
|
|
|
@header.each{|key, value|
|
* ext/json/lib/json/pure/generator.rb,
ext/json/lib/json/pure/parser.rb, ext/openssl/lib/openssl/x509.rb,
ext/win32ole/sample/olegen.rb, lib/date/format.rb, lib/irb/context.rb,
lib/irb/workspace.rb, lib/net/http.rb, lib/net/imap.rb,
lib/rdoc/generator.rb, lib/rdoc/markup/to_html.rb,
lib/rdoc/markup/to_latex.rb, lib/rdoc/parsers/parse_c.rb,
lib/rdoc/ri/formatter.rb, lib/rexml/parsers/baseparser.rb,
lib/rexml/quickpath.rb, lib/rexml/text.rb, lib/rss/parser.rb,
lib/uri/common.rb, lib/uri/generic.rb, lib/webrick/httpresponse.rb,
lib/webrick/httpservlet/filehandler.rb, lib/yaml/baseemitter.rb,
lib/yaml/encoding.rb: performance tuning arround String#gsub.
git-svn-id: svn+ssh://ci.ruby-lang.org/ruby/trunk@15442 b2dd03c8-39d4-4d8f-98ff-823fe69b080e
2008-02-12 01:18:06 -05:00
|
|
|
tmp = key.gsub(/\bwww|^te$|\b\w/){ $&.upcase }
|
2018-03-28 04:38:26 -04:00
|
|
|
data << "#{tmp}: #{check_header(value)}" << CRLF
|
2003-07-23 12:51:36 -04:00
|
|
|
}
|
|
|
|
@cookies.each{|cookie|
|
2018-03-28 04:38:26 -04:00
|
|
|
data << "Set-Cookie: " << check_header(cookie.to_s) << CRLF
|
2003-07-23 12:51:36 -04:00
|
|
|
}
|
|
|
|
data << CRLF
|
2018-03-28 04:05:46 -04:00
|
|
|
socket.write(data)
|
2003-07-23 12:51:36 -04:00
|
|
|
end
|
2018-03-28 04:38:26 -04:00
|
|
|
rescue InvalidHeader => e
|
|
|
|
@header.clear
|
|
|
|
@cookies.clear
|
|
|
|
set_error e
|
|
|
|
retry
|
2003-07-23 12:51:36 -04:00
|
|
|
end
|
|
|
|
|
2011-05-09 20:13:58 -04:00
|
|
|
##
|
|
|
|
# Sends the body on +socket+
|
|
|
|
|
2013-01-25 20:12:54 -05:00
|
|
|
def send_body(socket) # :nodoc:
|
2013-08-07 14:38:39 -04:00
|
|
|
if @body.respond_to? :readpartial then
|
|
|
|
send_body_io(socket)
|
2017-10-30 19:56:44 -04:00
|
|
|
elsif @body.respond_to?(:call) then
|
|
|
|
send_body_proc(socket)
|
2013-08-07 14:38:39 -04:00
|
|
|
else
|
|
|
|
send_body_string(socket)
|
2003-07-23 12:51:36 -04:00
|
|
|
end
|
|
|
|
end
|
|
|
|
|
2011-05-09 20:13:58 -04:00
|
|
|
##
|
|
|
|
# Redirects to +url+ with a WEBrick::HTTPStatus::Redirect +status+.
|
|
|
|
#
|
|
|
|
# Example:
|
|
|
|
#
|
|
|
|
# res.set_redirect WEBrick::HTTPStatus::TemporaryRedirect
|
|
|
|
|
2003-07-23 12:51:36 -04:00
|
|
|
def set_redirect(status, url)
|
2018-07-13 22:59:39 -04:00
|
|
|
url = URI(url).to_s
|
2014-06-24 04:48:46 -04:00
|
|
|
@body = "<HTML><A HREF=\"#{url}\">#{url}</A>.</HTML>\n"
|
2018-07-13 22:59:39 -04:00
|
|
|
@header['location'] = url
|
2003-07-23 12:51:36 -04:00
|
|
|
raise status
|
|
|
|
end
|
|
|
|
|
2011-05-09 20:13:58 -04:00
|
|
|
##
|
|
|
|
# Creates an error page for exception +ex+ with an optional +backtrace+
|
|
|
|
|
2003-07-23 12:51:36 -04:00
|
|
|
def set_error(ex, backtrace=false)
|
|
|
|
case ex
|
2009-03-05 22:56:38 -05:00
|
|
|
when HTTPStatus::Status
|
2003-07-23 12:51:36 -04:00
|
|
|
@keep_alive = false if HTTPStatus::error?(ex.code)
|
|
|
|
self.status = ex.code
|
2009-03-05 22:56:38 -05:00
|
|
|
else
|
2003-07-23 12:51:36 -04:00
|
|
|
@keep_alive = false
|
|
|
|
self.status = HTTPStatus::RC_INTERNAL_SERVER_ERROR
|
|
|
|
end
|
2010-08-15 23:41:12 -04:00
|
|
|
@header['content-type'] = "text/html; charset=ISO-8859-1"
|
2003-07-23 12:51:36 -04:00
|
|
|
|
|
|
|
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
|
|
|
|
|
2016-03-28 23:45:32 -04:00
|
|
|
error_body(backtrace, ex, host, port)
|
|
|
|
end
|
|
|
|
|
|
|
|
private
|
|
|
|
|
2018-03-28 04:38:26 -04:00
|
|
|
def check_header(header_value)
|
2019-09-30 23:29:18 -04:00
|
|
|
header_value = header_value.to_s
|
|
|
|
if /[\r\n]/ =~ header_value
|
2018-03-28 04:38:26 -04:00
|
|
|
raise InvalidHeader
|
|
|
|
else
|
|
|
|
header_value
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
2016-03-28 23:45:32 -04:00
|
|
|
# :stopdoc:
|
|
|
|
|
|
|
|
def error_body(backtrace, ex, host, port)
|
2003-07-23 12:51:36 -04:00
|
|
|
@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
|
|
|
|
|
|
|
|
def send_body_io(socket)
|
2004-10-21 06:10:52 -04:00
|
|
|
begin
|
|
|
|
if @request_method == "HEAD"
|
|
|
|
# do nothing
|
|
|
|
elsif chunked?
|
2017-10-30 21:37:37 -04:00
|
|
|
buf = ''
|
2012-04-07 17:50:34 -04:00
|
|
|
begin
|
2017-10-30 21:37:37 -04:00
|
|
|
@body.readpartial(@buffer_size, buf)
|
|
|
|
size = buf.bytesize
|
|
|
|
data = "#{size.to_s(16)}#{CRLF}#{buf}#{CRLF}"
|
2018-03-28 04:05:46 -04:00
|
|
|
socket.write(data)
|
2017-10-30 21:37:37 -04:00
|
|
|
data.clear
|
|
|
|
@sent_size += size
|
|
|
|
rescue EOFError
|
|
|
|
break
|
|
|
|
end while true
|
|
|
|
buf.clear
|
2018-03-28 04:05:46 -04:00
|
|
|
socket.write("0#{CRLF}#{CRLF}")
|
2004-10-21 06:10:52 -04:00
|
|
|
else
|
2018-03-28 04:05:57 -04:00
|
|
|
if %r{\Abytes (\d+)-(\d+)/\d+\z} =~ @header['content-range']
|
|
|
|
offset = $1.to_i
|
|
|
|
size = $2.to_i - offset + 1
|
|
|
|
else
|
|
|
|
offset = nil
|
|
|
|
size = @header['content-length']
|
|
|
|
size = size.to_i if size
|
|
|
|
end
|
2018-03-28 09:27:35 -04:00
|
|
|
begin
|
|
|
|
@sent_size = IO.copy_stream(@body, socket, size, offset)
|
|
|
|
rescue NotImplementedError
|
|
|
|
@body.seek(offset, IO::SEEK_SET)
|
|
|
|
@sent_size = IO.copy_stream(@body, socket, size)
|
|
|
|
end
|
2003-07-23 12:51:36 -04:00
|
|
|
end
|
2004-10-21 06:10:52 -04:00
|
|
|
ensure
|
|
|
|
@body.close
|
2003-07-23 12:51:36 -04:00
|
|
|
end
|
2019-07-10 20:18:41 -04:00
|
|
|
remove_body_tempfile
|
2003-07-23 12:51:36 -04:00
|
|
|
end
|
|
|
|
|
|
|
|
def send_body_string(socket)
|
|
|
|
if @request_method == "HEAD"
|
|
|
|
# do nothing
|
|
|
|
elsif chunked?
|
2010-11-08 15:59:01 -05:00
|
|
|
body ? @body.bytesize : 0
|
2006-05-18 09:42:52 -04:00
|
|
|
while buf = @body[@sent_size, @buffer_size]
|
2003-07-23 12:51:36 -04:00
|
|
|
break if buf.empty?
|
2017-10-30 21:37:37 -04:00
|
|
|
size = buf.bytesize
|
|
|
|
data = "#{size.to_s(16)}#{CRLF}#{buf}#{CRLF}"
|
|
|
|
buf.clear
|
2018-03-28 04:05:46 -04:00
|
|
|
socket.write(data)
|
2017-10-30 21:37:37 -04:00
|
|
|
@sent_size += size
|
2003-07-23 12:51:36 -04:00
|
|
|
end
|
2018-03-28 04:05:46 -04:00
|
|
|
socket.write("0#{CRLF}#{CRLF}")
|
2003-07-23 12:51:36 -04:00
|
|
|
else
|
2008-11-08 04:41:24 -05:00
|
|
|
if @body && @body.bytesize > 0
|
2018-03-28 04:05:46 -04:00
|
|
|
socket.write(@body)
|
2008-11-08 04:41:24 -05:00
|
|
|
@sent_size = @body.bytesize
|
2003-07-23 12:51:36 -04:00
|
|
|
end
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
2017-10-30 19:56:44 -04:00
|
|
|
def send_body_proc(socket)
|
|
|
|
if @request_method == "HEAD"
|
|
|
|
# do nothing
|
|
|
|
elsif chunked?
|
|
|
|
@body.call(ChunkedWrapper.new(socket, self))
|
2018-03-28 04:05:46 -04:00
|
|
|
socket.write("0#{CRLF}#{CRLF}")
|
2017-10-30 19:56:44 -04:00
|
|
|
else
|
|
|
|
size = @header['content-length'].to_i
|
2019-07-10 20:18:41 -04:00
|
|
|
if @bodytempfile
|
|
|
|
@bodytempfile.rewind
|
|
|
|
IO.copy_stream(@bodytempfile, socket)
|
|
|
|
else
|
|
|
|
@body.call(socket)
|
|
|
|
end
|
2017-10-30 19:56:44 -04:00
|
|
|
@sent_size = size
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
|
|
|
class ChunkedWrapper
|
|
|
|
def initialize(socket, resp)
|
|
|
|
@socket = socket
|
|
|
|
@resp = resp
|
|
|
|
end
|
|
|
|
|
|
|
|
def write(buf)
|
2018-03-28 04:06:13 -04:00
|
|
|
return 0 if buf.empty?
|
2017-10-30 19:56:44 -04:00
|
|
|
socket = @socket
|
|
|
|
@resp.instance_eval {
|
|
|
|
size = buf.bytesize
|
|
|
|
data = "#{size.to_s(16)}#{CRLF}#{buf}#{CRLF}"
|
2018-03-28 04:05:46 -04:00
|
|
|
socket.write(data)
|
2017-10-30 19:56:44 -04:00
|
|
|
data.clear
|
|
|
|
@sent_size += size
|
2018-03-28 04:06:13 -04:00
|
|
|
size
|
2017-10-30 19:56:44 -04:00
|
|
|
}
|
|
|
|
end
|
2018-03-28 04:06:13 -04:00
|
|
|
|
|
|
|
def <<(*buf)
|
|
|
|
write(buf)
|
|
|
|
self
|
|
|
|
end
|
2017-10-30 19:56:44 -04:00
|
|
|
end
|
|
|
|
|
2018-03-28 04:05:46 -04:00
|
|
|
# preserved for compatibility with some 3rd-party handlers
|
2003-07-23 12:51:36 -04:00
|
|
|
def _write_data(socket, data)
|
|
|
|
socket << data
|
|
|
|
end
|
2013-01-25 20:12:54 -05:00
|
|
|
|
|
|
|
# :startdoc:
|
2003-07-23 12:51:36 -04:00
|
|
|
end
|
2013-01-25 20:12:54 -05:00
|
|
|
|
2003-07-23 12:51:36 -04:00
|
|
|
end
|