diff options
Diffstat (limited to 'lib/net/http/generic_request.rb')
| -rw-r--r-- | lib/net/http/generic_request.rb | 218 |
1 files changed, 159 insertions, 59 deletions
diff --git a/lib/net/http/generic_request.rb b/lib/net/http/generic_request.rb index 6e79bb9c2e..5b01ea4abd 100644 --- a/lib/net/http/generic_request.rb +++ b/lib/net/http/generic_request.rb @@ -1,40 +1,44 @@ -# HTTPGenericRequest is the parent of the HTTPRequest class. -# Do not use this directly; use a subclass of HTTPRequest. +# frozen_string_literal: true # -# Mixes in the HTTPHeader module to provide easier access to HTTP headers. +# \HTTPGenericRequest is the parent of the Net::HTTPRequest class. +# +# Do not use this directly; instead, use a subclass of Net::HTTPRequest. +# +# == About the Examples +# +# :include: doc/net-http/examples.rdoc # class Net::HTTPGenericRequest include Net::HTTPHeader - def initialize(m, reqbody, resbody, uri_or_path, initheader = nil) + def initialize(m, reqbody, resbody, uri_or_path, initheader = nil) # :nodoc: @method = m @request_has_body = reqbody @response_has_body = resbody if URI === uri_or_path then + raise ArgumentError, "not an HTTP URI" unless URI::HTTP === uri_or_path + hostname = uri_or_path.host + raise ArgumentError, "no host component for URI" unless (hostname && hostname.length > 0) @uri = uri_or_path.dup - host = @uri.hostname - host += ":#{@uri.port}" if @uri.port != @uri.class::DEFAULT_PORT - path = uri_or_path.request_uri + @path = uri_or_path.request_uri + raise ArgumentError, "no HTTP request path given" unless @path else @uri = nil - host = nil - path = uri_or_path + raise ArgumentError, "no HTTP request path given" unless uri_or_path + raise ArgumentError, "HTTP request path is empty" if uri_or_path.empty? + @path = uri_or_path.dup end - raise ArgumentError, "no HTTP request path given" unless path - raise ArgumentError, "HTTP request path is empty" if path.empty? - @path = path - @decode_content = false - if @response_has_body and Net::HTTP::HAVE_ZLIB then + if Net::HTTP::HAVE_ZLIB then if !initheader || !initheader.keys.any? { |k| %w[accept-encoding range].include? k.downcase } then - @decode_content = true + @decode_content = true if @response_has_body initheader = initheader ? initheader.dup : {} initheader["accept-encoding"] = "gzip;q=1.0,deflate;q=0.6,identity;q=0.3" @@ -44,25 +48,82 @@ class Net::HTTPGenericRequest initialize_http_header initheader self['Accept'] ||= '*/*' self['User-Agent'] ||= 'Ruby' - self['Host'] ||= host + self['Host'] ||= @uri.authority if @uri @body = nil @body_stream = nil @body_data = nil end + # Returns the string method name for the request: + # + # Net::HTTP::Get.new(uri).method # => "GET" + # Net::HTTP::Post.new(uri).method # => "POST" + # attr_reader :method + + # Returns the string path for the request: + # + # Net::HTTP::Get.new(uri).path # => "/" + # Net::HTTP::Post.new('example.com').path # => "example.com" + # attr_reader :path + + # Returns the URI object for the request, or +nil+ if none: + # + # Net::HTTP::Get.new(uri).uri + # # => #<URI::HTTPS https://jsonplaceholder.typicode.com/> + # Net::HTTP::Get.new('example.com').uri # => nil + # attr_reader :uri - # Automatically set to false if the user sets the Accept-Encoding header. - # This indicates they wish to handle Content-encoding in responses - # themselves. + # Returns +false+ if the request's header <tt>'Accept-Encoding'</tt> + # has been set manually or deleted + # (indicating that the user intends to handle encoding in the response), + # +true+ otherwise: + # + # req = Net::HTTP::Get.new(uri) # => #<Net::HTTP::Get GET> + # req['Accept-Encoding'] # => "gzip;q=1.0,deflate;q=0.6,identity;q=0.3" + # req.decode_content # => true + # req['Accept-Encoding'] = 'foo' + # req.decode_content # => false + # req.delete('Accept-Encoding') + # req.decode_content # => false + # attr_reader :decode_content + # Returns a string representation of the request: + # + # Net::HTTP::Post.new(uri).inspect # => "#<Net::HTTP::Post POST>" + # def inspect "\#<#{self.class} #{@method}>" end + # Returns a string representation of the request with the details for pp: + # + # require 'pp' + # post = Net::HTTP::Post.new(uri) + # post.inspect # => "#<Net::HTTP::Post POST>" + # post.pretty_inspect + # # => #<Net::HTTP::Post + # POST + # path="/" + # headers={"accept-encoding" => ["gzip;q=1.0,deflate;q=0.6,identity;q=0.3"], + # "accept" => ["*/*"], + # "user-agent" => ["Ruby"], + # "host" => ["www.ruby-lang.org"]}> + # + def pretty_print(q) + q.object_group(self) { + q.breakable + q.text @method + q.breakable + q.text "path="; q.pp @path + q.breakable + q.text "headers="; q.pp to_hash + } + end + ## # Don't automatically decode response content-encoding if the user indicates # they want to handle it. @@ -73,21 +134,45 @@ class Net::HTTPGenericRequest super key, val end + # Returns whether the request may have a body: + # + # Net::HTTP::Post.new(uri).request_body_permitted? # => true + # Net::HTTP::Get.new(uri).request_body_permitted? # => false + # def request_body_permitted? @request_has_body end + # Returns whether the response may have a body: + # + # Net::HTTP::Post.new(uri).response_body_permitted? # => true + # Net::HTTP::Head.new(uri).response_body_permitted? # => false + # def response_body_permitted? @response_has_body end - def body_exist? - warn "Net::HTTPRequest#body_exist? is obsolete; use response_body_permitted?" if $VERBOSE + def body_exist? # :nodoc: + warn "Net::HTTPRequest#body_exist? is obsolete; use response_body_permitted?", uplevel: 1 if $VERBOSE response_body_permitted? end + # Returns the string body for the request, or +nil+ if there is none: + # + # req = Net::HTTP::Post.new(uri) + # req.body # => nil + # req.body = '{"title": "foo","body": "bar","userId": 1}' + # req.body # => "{\"title\": \"foo\",\"body\": \"bar\",\"userId\": 1}" + # attr_reader :body + # Sets the body for the request: + # + # req = Net::HTTP::Post.new(uri) + # req.body # => nil + # req.body = '{"title": "foo","body": "bar","userId": 1}' + # req.body # => "{\"title\": \"foo\",\"body\": \"bar\",\"userId\": 1}" + # def body=(str) @body = str @body_stream = nil @@ -95,8 +180,24 @@ class Net::HTTPGenericRequest str end + # Returns the body stream object for the request, or +nil+ if there is none: + # + # req = Net::HTTP::Post.new(uri) # => #<Net::HTTP::Post POST> + # req.body_stream # => nil + # require 'stringio' + # req.body_stream = StringIO.new('xyzzy') # => #<StringIO:0x0000027d1e5affa8> + # req.body_stream # => #<StringIO:0x0000027d1e5affa8> + # attr_reader :body_stream + # Sets the body stream for the request: + # + # req = Net::HTTP::Post.new(uri) # => #<Net::HTTP::Post POST> + # req.body_stream # => nil + # require 'stringio' + # req.body_stream = StringIO.new('xyzzy') # => #<StringIO:0x0000027d1e5affa8> + # req.body_stream # => #<StringIO:0x0000027d1e5affa8> + # def body_stream=(input) @body = nil @body_stream = input @@ -117,15 +218,6 @@ class Net::HTTPGenericRequest # def exec(sock, ver, path) #:nodoc: internal use only - if @uri - if @uri.port == @uri.default_port - # [Bug #7650] Amazon ECS API and GFE/1.3 disallow extra default port number - self['host'] = @uri.host - else - self['host'] = "#{@uri.host}:#{@uri.port}" - end - end - if @body send_request_with_body sock, ver, path, @body elsif @body_stream @@ -137,25 +229,40 @@ class Net::HTTPGenericRequest end end - def update_uri(host, port, ssl) # :nodoc: internal use only + def update_uri(addr, port, ssl) # :nodoc: internal use only + # reflect the connection and @path to @uri return unless @uri - @uri.host ||= host - @uri.port = port - - scheme = ssl ? 'https' : 'http' + if ssl + scheme = 'https' + klass = URI::HTTPS + else + scheme = 'http' + klass = URI::HTTP + end + if host = self['host'] + host = URI.parse("//#{host}").host # Remove a port component from the existing Host header + elsif host = @uri.host + else + host = addr + end # convert the class of the URI - unless scheme == @uri.scheme then - new_uri = @uri.to_s.sub(/^https?/, scheme) - @uri = URI new_uri + if @uri.is_a?(klass) + @uri.host = host + @uri.port = port + else + @uri = klass.new( + scheme, @uri.userinfo, + host, port, nil, + @uri.path, nil, @uri.query, nil) end - - @uri end private + # :stopdoc: + class Chunker #:nodoc: def initialize(sock) @sock = sock @@ -164,9 +271,8 @@ class Net::HTTPGenericRequest 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 = buf.bytesize + @sock.write("#{rv.to_s(16)}\r\n", buf, "\r\n") rv end @@ -178,7 +284,6 @@ class Net::HTTPGenericRequest 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 @@ -189,7 +294,6 @@ class Net::HTTPGenericRequest 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? @@ -197,9 +301,7 @@ class Net::HTTPGenericRequest 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) + IO.copy_stream(f, sock) end end @@ -236,7 +338,7 @@ class Net::HTTPGenericRequest boundary ||= SecureRandom.urlsafe_base64(40) chunked_p = chunked? - buf = '' + buf = +'' params.each do |key, value, h={}| key = quote_string(key, charset) filename = @@ -282,7 +384,7 @@ class Net::HTTPGenericRequest def quote_string(str, charset) str = str.encode(charset, fallback:->(c){'&#%d;'%c.encode("UTF-8").ord}) if charset - str = str.gsub(/[\\"]/, '\\\\\&') + str.gsub(/[\\"]/, '\\\\\&') end def flush_buffer(out, buf, chunked_p) @@ -293,12 +395,6 @@ class Net::HTTPGenericRequest 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. @@ -306,7 +402,7 @@ class Net::HTTPGenericRequest 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) + if sock.io.to_io.wait_readable(sock.continue_timeout) res = Net::HTTPResponse.read_new(sock) unless res.kind_of?(Net::HTTPContinue) res.decode_content = @decode_content @@ -317,7 +413,12 @@ class Net::HTTPGenericRequest end def write_header(sock, ver, path) - buf = "#{@method} #{path} HTTP/#{ver}\r\n" + reqline = "#{@method} #{path} HTTP/#{ver}" + if /[\r\n]/ =~ reqline + raise ArgumentError, "A Request-Line must not contain CR or LF" + end + buf = +'' + buf << reqline << "\r\n" each_capitalized do |k,v| buf << "#{k}: #{v}\r\n" end @@ -326,4 +427,3 @@ class Net::HTTPGenericRequest end end - |
