mirror of
https://github.com/ruby/ruby.git
synced 2022-11-09 12:17:21 -05:00
* lib/net/http.rb: Broke up Net::HTTP into individual files.
[ruby-trunk - Feature #6435] * lib/net/http/backward.rb: ditto. * lib/net/http/response.rb: ditto. * lib/net/http/exceptions.rb: ditto. * lib/net/http/responses.rb: ditto. * lib/net/http/generic_request.rb: ditto. * lib/net/http/header.rb: ditto. * lib/net/http/request.rb: ditto. * lib/net/http/proxy_delta.rb: ditto. * lib/net/http/requests.rb: ditto. git-svn-id: svn+ssh://ci.ruby-lang.org/ruby/trunk@35761 b2dd03c8-39d4-4d8f-98ff-823fe69b080e
This commit is contained in:
parent
8225fd3d4c
commit
510bbe015d
11 changed files with 1448 additions and 1437 deletions
254
lib/net/http/generic_request.rb
Normal file
254
lib/net/http/generic_request.rb
Normal file
|
@ -0,0 +1,254 @@
|
|||
# HTTPGenericRequest is the parent of the HTTPRequest class.
|
||||
# Do not use this directly; use a subclass of HTTPRequest.
|
||||
#
|
||||
# Mixes in the HTTPHeader module to provide easier access to HTTP headers.
|
||||
#
|
||||
class Net::HTTPGenericRequest
|
||||
|
||||
include Net::HTTPHeader
|
||||
|
||||
def initialize(m, reqbody, resbody, path, initheader = nil)
|
||||
@method = m
|
||||
@request_has_body = reqbody
|
||||
@response_has_body = resbody
|
||||
raise ArgumentError, "no HTTP request path given" unless path
|
||||
raise ArgumentError, "HTTP request path is empty" if path.empty?
|
||||
@path = path
|
||||
initialize_http_header initheader
|
||||
self['Accept'] ||= '*/*'
|
||||
self['User-Agent'] ||= 'Ruby'
|
||||
@body = nil
|
||||
@body_stream = nil
|
||||
@body_data = nil
|
||||
end
|
||||
|
||||
attr_reader :method
|
||||
attr_reader :path
|
||||
|
||||
def inspect
|
||||
"\#<#{self.class} #{@method}>"
|
||||
end
|
||||
|
||||
def request_body_permitted?
|
||||
@request_has_body
|
||||
end
|
||||
|
||||
def response_body_permitted?
|
||||
@response_has_body
|
||||
end
|
||||
|
||||
def body_exist?
|
||||
warn "Net::HTTPRequest#body_exist? is obsolete; use response_body_permitted?" if $VERBOSE
|
||||
response_body_permitted?
|
||||
end
|
||||
|
||||
attr_reader :body
|
||||
|
||||
def body=(str)
|
||||
@body = str
|
||||
@body_stream = nil
|
||||
@body_data = nil
|
||||
str
|
||||
end
|
||||
|
||||
attr_reader :body_stream
|
||||
|
||||
def body_stream=(input)
|
||||
@body = nil
|
||||
@body_stream = input
|
||||
@body_data = nil
|
||||
input
|
||||
end
|
||||
|
||||
def set_body_internal(str) #:nodoc: internal use only
|
||||
raise ArgumentError, "both of body argument and HTTPRequest#body set" if str and (@body or @body_stream)
|
||||
self.body = str if str
|
||||
end
|
||||
|
||||
#
|
||||
# write
|
||||
#
|
||||
|
||||
def exec(sock, ver, path) #:nodoc: internal use only
|
||||
if @body
|
||||
send_request_with_body sock, ver, path, @body
|
||||
elsif @body_stream
|
||||
send_request_with_body_stream sock, ver, path, @body_stream
|
||||
elsif @body_data
|
||||
send_request_with_body_data sock, ver, path, @body_data
|
||||
else
|
||||
write_header sock, ver, path
|
||||
end
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
class Chunker #:nodoc:
|
||||
def initialize(sock)
|
||||
@sock = sock
|
||||
@prev = nil
|
||||
end
|
||||
|
||||
def write(buf)
|
||||
# avoid memcpy() of buf, buf can huge and eat memory bandwidth
|
||||
@sock.write("#{buf.bytesize.to_s(16)}\r\n")
|
||||
rv = @sock.write(buf)
|
||||
@sock.write("\r\n")
|
||||
rv
|
||||
end
|
||||
|
||||
def finish
|
||||
@sock.write("0\r\n\r\n")
|
||||
end
|
||||
end
|
||||
|
||||
def send_request_with_body(sock, ver, path, body)
|
||||
self.content_length = body.bytesize
|
||||
delete 'Transfer-Encoding'
|
||||
supply_default_content_type
|
||||
write_header sock, ver, path
|
||||
wait_for_continue sock, ver if sock.continue_timeout
|
||||
sock.write body
|
||||
end
|
||||
|
||||
def send_request_with_body_stream(sock, ver, path, f)
|
||||
unless content_length() or chunked?
|
||||
raise ArgumentError,
|
||||
"Content-Length not given and Transfer-Encoding is not `chunked'"
|
||||
end
|
||||
supply_default_content_type
|
||||
write_header sock, ver, path
|
||||
wait_for_continue sock, ver if sock.continue_timeout
|
||||
if chunked?
|
||||
chunker = Chunker.new(sock)
|
||||
IO.copy_stream(f, chunker)
|
||||
chunker.finish
|
||||
else
|
||||
# copy_stream can sendfile() to sock.io unless we use SSL.
|
||||
# If sock.io is an SSLSocket, copy_stream will hit SSL_write()
|
||||
IO.copy_stream(f, sock.io)
|
||||
end
|
||||
end
|
||||
|
||||
def send_request_with_body_data(sock, ver, path, params)
|
||||
if /\Amultipart\/form-data\z/i !~ self.content_type
|
||||
self.content_type = 'application/x-www-form-urlencoded'
|
||||
return send_request_with_body(sock, ver, path, URI.encode_www_form(params))
|
||||
end
|
||||
|
||||
opt = @form_option.dup
|
||||
require 'securerandom' unless defined?(SecureRandom)
|
||||
opt[:boundary] ||= SecureRandom.urlsafe_base64(40)
|
||||
self.set_content_type(self.content_type, boundary: opt[:boundary])
|
||||
if chunked?
|
||||
write_header sock, ver, path
|
||||
encode_multipart_form_data(sock, params, opt)
|
||||
else
|
||||
require 'tempfile'
|
||||
file = Tempfile.new('multipart')
|
||||
file.binmode
|
||||
encode_multipart_form_data(file, params, opt)
|
||||
file.rewind
|
||||
self.content_length = file.size
|
||||
write_header sock, ver, path
|
||||
IO.copy_stream(file, sock)
|
||||
end
|
||||
end
|
||||
|
||||
def encode_multipart_form_data(out, params, opt)
|
||||
charset = opt[:charset]
|
||||
boundary = opt[:boundary]
|
||||
require 'securerandom' unless defined?(SecureRandom)
|
||||
boundary ||= SecureRandom.urlsafe_base64(40)
|
||||
chunked_p = chunked?
|
||||
|
||||
buf = ''
|
||||
params.each do |key, value, h={}|
|
||||
key = quote_string(key, charset)
|
||||
filename =
|
||||
h.key?(:filename) ? h[:filename] :
|
||||
value.respond_to?(:to_path) ? File.basename(value.to_path) :
|
||||
nil
|
||||
|
||||
buf << "--#{boundary}\r\n"
|
||||
if filename
|
||||
filename = quote_string(filename, charset)
|
||||
type = h[:content_type] || 'application/octet-stream'
|
||||
buf << "Content-Disposition: form-data; " \
|
||||
"name=\"#{key}\"; filename=\"#{filename}\"\r\n" \
|
||||
"Content-Type: #{type}\r\n\r\n"
|
||||
if !out.respond_to?(:write) || !value.respond_to?(:read)
|
||||
# if +out+ is not an IO or +value+ is not an IO
|
||||
buf << (value.respond_to?(:read) ? value.read : value)
|
||||
elsif value.respond_to?(:size) && chunked_p
|
||||
# if +out+ is an IO and +value+ is a File, use IO.copy_stream
|
||||
flush_buffer(out, buf, chunked_p)
|
||||
out << "%x\r\n" % value.size if chunked_p
|
||||
IO.copy_stream(value, out)
|
||||
out << "\r\n" if chunked_p
|
||||
else
|
||||
# +out+ is an IO, and +value+ is not a File but an IO
|
||||
flush_buffer(out, buf, chunked_p)
|
||||
1 while flush_buffer(out, value.read(4096), chunked_p)
|
||||
end
|
||||
else
|
||||
# non-file field:
|
||||
# HTML5 says, "The parts of the generated multipart/form-data
|
||||
# resource that correspond to non-file fields must not have a
|
||||
# Content-Type header specified."
|
||||
buf << "Content-Disposition: form-data; name=\"#{key}\"\r\n\r\n"
|
||||
buf << (value.respond_to?(:read) ? value.read : value)
|
||||
end
|
||||
buf << "\r\n"
|
||||
end
|
||||
buf << "--#{boundary}--\r\n"
|
||||
flush_buffer(out, buf, chunked_p)
|
||||
out << "0\r\n\r\n" if chunked_p
|
||||
end
|
||||
|
||||
def quote_string(str, charset)
|
||||
str = str.encode(charset, fallback:->(c){'&#%d;'%c.encode("UTF-8").ord}) if charset
|
||||
str = str.gsub(/[\\"]/, '\\\\\&')
|
||||
end
|
||||
|
||||
def flush_buffer(out, buf, chunked_p)
|
||||
return unless buf
|
||||
out << "%x\r\n"%buf.bytesize if chunked_p
|
||||
out << buf
|
||||
out << "\r\n" if chunked_p
|
||||
buf.clear
|
||||
end
|
||||
|
||||
def supply_default_content_type
|
||||
return if content_type()
|
||||
warn 'net/http: warning: Content-Type did not set; using application/x-www-form-urlencoded' if $VERBOSE
|
||||
set_content_type 'application/x-www-form-urlencoded'
|
||||
end
|
||||
|
||||
##
|
||||
# Waits up to the continue timeout for a response from the server provided
|
||||
# we're speaking HTTP 1.1 and are expecting a 100-continue response.
|
||||
|
||||
def wait_for_continue(sock, ver)
|
||||
if ver >= '1.1' and @header['expect'] and
|
||||
@header['expect'].include?('100-continue')
|
||||
if IO.select([sock.io], nil, nil, sock.continue_timeout)
|
||||
res = Net::HTTPResponse.read_new(sock)
|
||||
unless res.kind_of?(Net::HTTPContinue)
|
||||
throw :response, res
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
def write_header(sock, ver, path)
|
||||
buf = "#{@method} #{path} HTTP/#{ver}\r\n"
|
||||
each_capitalized do |k,v|
|
||||
buf << "#{k}: #{v}\r\n"
|
||||
end
|
||||
buf << "\r\n"
|
||||
sock.write buf
|
||||
end
|
||||
|
||||
end
|
||||
|
Loading…
Add table
Add a link
Reference in a new issue