diff options
Diffstat (limited to 'lib/net/http.rb')
| -rw-r--r-- | lib/net/http.rb | 278 |
1 files changed, 195 insertions, 83 deletions
diff --git a/lib/net/http.rb b/lib/net/http.rb index 387df4b8f4..53295fe90c 100644 --- a/lib/net/http.rb +++ b/lib/net/http.rb @@ -37,7 +37,7 @@ module Net #:nodoc: # For information about \HTTP, see: # # - {Hypertext Transfer Protocol}[https://en.wikipedia.org/wiki/Hypertext_Transfer_Protocol]. - # - {Technical overview}[https://en.wikipedia.org/wiki/Hypertext_Transfer_Protocol#Technical_overview]. + # - {Technology}[https://en.wikipedia.org/wiki/Hypertext_Transfer_Protocol#Technology]. # # == About the Examples # @@ -67,10 +67,12 @@ module Net #:nodoc: # Net::HTTP.post(uri, data) # params = {title: 'foo', body: 'bar', userId: 1} # Net::HTTP.post_form(uri, params) + # data = '{"title": "foo", "body": "bar", "userId": 1}' + # Net::HTTP.put(uri, data) # # - If performance is important, consider using sessions, which lower request overhead. # This {session}[rdoc-ref:Net::HTTP@Sessions] has multiple requests for - # {HTTP methods}[https://en.wikipedia.org/wiki/Hypertext_Transfer_Protocol#Request_methods] + # {HTTP methods}[https://en.wikipedia.org/wiki/Hypertext_Transfer_Protocol#Method] # and {WebDAV methods}[https://en.wikipedia.org/wiki/WebDAV#Implementation]: # # Net::HTTP.start(hostname) do |http| @@ -196,7 +198,7 @@ module Net #:nodoc: # In the block, you can use these instance methods, # each of which that sends a single request: # - # - {HTTP methods}[https://en.wikipedia.org/wiki/Hypertext_Transfer_Protocol#Request_methods]: + # - {HTTP methods}[https://en.wikipedia.org/wiki/Hypertext_Transfer_Protocol#Method]: # # - #get, #request_get: GET. # - #head, #request_head: HEAD. @@ -445,7 +447,7 @@ module Net #:nodoc: # if the response has header <tt>'Content-Range'</tt>. # # Otherwise decompression (or not) depends on the value of header - # {Content-Encoding}[https://en.wikipedia.org/wiki/List_of_HTTP_header_fields#content-encoding-response-header]: + # {Content-Encoding}[https://en.wikipedia.org/wiki/List_of_HTTP_header_fields#Content-Encoding_2]: # # - <tt>'deflate'</tt>, <tt>'gzip'</tt>, or <tt>'x-gzip'</tt>: # decompresses the body and deletes the header. @@ -456,6 +458,10 @@ module Net #:nodoc: # # == What's Here # + # First, what's elsewhere. Class Net::HTTP: + # + # - Inherits from {class Object}[rdoc-ref:Object#class-object-whats-here]. + # # This is a categorized summary of methods and attributes. # # === \Net::HTTP Objects @@ -469,8 +475,7 @@ module Net #:nodoc: # # - {::start}[rdoc-ref:Net::HTTP.start]: # Begins a new session in a new \Net::HTTP object. - # - {#started?}[rdoc-ref:Net::HTTP#started?] - # (aliased as {#active?}[rdoc-ref:Net::HTTP#active?]): + # - {#started?}[rdoc-ref:Net::HTTP#started?]: # Returns whether in a session. # - {#finish}[rdoc-ref:Net::HTTP#finish]: # Ends an active session. @@ -520,6 +525,8 @@ module Net #:nodoc: # Sends a POST request with form data and returns a response object. # - {::post}[rdoc-ref:Net::HTTP.post]: # Sends a POST request with data and returns a response object. + # - {::put}[rdoc-ref:Net::HTTP.put]: + # Sends a PUT request with data and returns a response object. # - {#copy}[rdoc-ref:Net::HTTP#copy]: # Sends a COPY request and returns a response object. # - {#delete}[rdoc-ref:Net::HTTP#delete]: @@ -548,18 +555,15 @@ module Net #:nodoc: # Sends a PUT request and returns a response object. # - {#request}[rdoc-ref:Net::HTTP#request]: # Sends a request and returns a response object. - # - {#request_get}[rdoc-ref:Net::HTTP#request_get] - # (aliased as {#get2}[rdoc-ref:Net::HTTP#get2]): + # - {#request_get}[rdoc-ref:Net::HTTP#request_get]: # Sends a GET request and forms a response object; # if a block given, calls the block with the object, # otherwise returns the object. - # - {#request_head}[rdoc-ref:Net::HTTP#request_head] - # (aliased as {#head2}[rdoc-ref:Net::HTTP#head2]): + # - {#request_head}[rdoc-ref:Net::HTTP#request_head]: # Sends a HEAD request and forms a response object; # if a block given, calls the block with the object, # otherwise returns the object. - # - {#request_post}[rdoc-ref:Net::HTTP#request_post] - # (aliased as {#post2}[rdoc-ref:Net::HTTP#post2]): + # - {#request_post}[rdoc-ref:Net::HTTP#request_post]: # Sends a POST request and forms a response object; # if a block given, calls the block with the object, # otherwise returns the object. @@ -597,8 +601,7 @@ module Net #:nodoc: # Returns whether +self+ is a proxy class. # - {#proxy?}[rdoc-ref:Net::HTTP#proxy?]: # Returns whether +self+ has a proxy. - # - {#proxy_address}[rdoc-ref:Net::HTTP#proxy_address] - # (aliased as {#proxyaddr}[rdoc-ref:Net::HTTP#proxyaddr]): + # - {#proxy_address}[rdoc-ref:Net::HTTP#proxy_address]: # Returns the proxy address. # - {#proxy_from_env?}[rdoc-ref:Net::HTTP#proxy_from_env?]: # Returns whether the proxy is taken from an environment variable. @@ -710,8 +713,7 @@ module Net #:nodoc: # === \HTTP Version # # - {::version_1_2?}[rdoc-ref:Net::HTTP.version_1_2?] - # (aliased as {::is_version_1_2?}[rdoc-ref:Net::HTTP.is_version_1_2?] - # and {::version_1_2}[rdoc-ref:Net::HTTP.version_1_2]): + # (aliased as {::version_1_2}[rdoc-ref:Net::HTTP.version_1_2]): # Returns true; retained for compatibility. # # === Debugging @@ -722,7 +724,7 @@ module Net #:nodoc: class HTTP < Protocol # :stopdoc: - VERSION = "0.4.1" + VERSION = "0.9.1" HTTPVersion = '1.1' begin require 'zlib' @@ -889,6 +891,39 @@ module Net #:nodoc: } end + # Sends a PUT request to the server; returns a Net::HTTPResponse object. + # + # Argument +url+ must be a URL; + # argument +data+ must be a string: + # + # _uri = uri.dup + # _uri.path = '/posts' + # data = '{"title": "foo", "body": "bar", "userId": 1}' + # headers = {'content-type': 'application/json'} + # res = Net::HTTP.put(_uri, data, headers) # => #<Net::HTTPCreated 201 Created readbody=true> + # puts res.body + # + # Output: + # + # { + # "title": "foo", + # "body": "bar", + # "userId": 1, + # "id": 101 + # } + # + # Related: + # + # - Net::HTTP::Put: request class for \HTTP method +PUT+. + # - Net::HTTP#put: convenience method for \HTTP method +PUT+. + # + def HTTP.put(url, data, header = nil) + start(url.hostname, url.port, + :use_ssl => url.scheme == 'https' ) {|http| + http.put(url, data, header) + } + end + # # \HTTP session management # @@ -1062,7 +1097,7 @@ module Net #:nodoc: # For proxy-defining arguments +p_addr+ through +p_no_proxy+, # see {Proxy Server}[rdoc-ref:Net::HTTP@Proxy+Server]. # - def HTTP.new(address, port = nil, p_addr = :ENV, p_port = nil, p_user = nil, p_pass = nil, p_no_proxy = nil) + def HTTP.new(address, port = nil, p_addr = :ENV, p_port = nil, p_user = nil, p_pass = nil, p_no_proxy = nil, p_use_ssl = nil) http = super address, port if proxy_class? then # from Net::HTTP::Proxy() @@ -1071,6 +1106,7 @@ module Net #:nodoc: http.proxy_port = @proxy_port http.proxy_user = @proxy_user http.proxy_pass = @proxy_pass + http.proxy_use_ssl = @proxy_use_ssl elsif p_addr == :ENV then http.proxy_from_env = true else @@ -1082,34 +1118,68 @@ module Net #:nodoc: http.proxy_port = p_port || default_port http.proxy_user = p_user http.proxy_pass = p_pass + http.proxy_use_ssl = p_use_ssl end http end + class << HTTP + # Allows to set the default configuration that will be used + # when creating a new connection. + # + # Example: + # + # Net::HTTP.default_configuration = { + # read_timeout: 1, + # write_timeout: 1 + # } + # http = Net::HTTP.new(hostname) + # http.open_timeout # => 60 + # http.read_timeout # => 1 + # http.write_timeout # => 1 + # + attr_accessor :default_configuration + end + # Creates a new \Net::HTTP object for the specified server address, # without opening the TCP connection or initializing the \HTTP session. # The +address+ should be a DNS hostname or IP address. def initialize(address, port = nil) # :nodoc: + defaults = { + keep_alive_timeout: 2, + close_on_empty_response: false, + open_timeout: 60, + read_timeout: 60, + write_timeout: 60, + continue_timeout: nil, + max_retries: 1, + debug_output: nil, + response_body_encoding: false, + ignore_eof: true + } + options = defaults.merge(self.class.default_configuration || {}) + @address = address @port = (port || HTTP.default_port) @ipaddr = nil @local_host = nil @local_port = nil @curr_http_version = HTTPVersion - @keep_alive_timeout = 2 + @keep_alive_timeout = options[:keep_alive_timeout] @last_communicated = nil - @close_on_empty_response = false + @close_on_empty_response = options[:close_on_empty_response] @socket = nil @started = false - @open_timeout = 60 - @read_timeout = 60 - @write_timeout = 60 - @continue_timeout = nil - @max_retries = 1 - @debug_output = nil - @response_body_encoding = false - @ignore_eof = true + @open_timeout = options[:open_timeout] + @read_timeout = options[:read_timeout] + @write_timeout = options[:write_timeout] + @continue_timeout = options[:continue_timeout] + @max_retries = options[:max_retries] + @debug_output = options[:debug_output] + @response_body_encoding = options[:response_body_encoding] + @ignore_eof = options[:ignore_eof] + @tcpsocket_supports_open_timeout = nil @proxy_from_env = false @proxy_uri = nil @@ -1117,6 +1187,7 @@ module Net #:nodoc: @proxy_port = nil @proxy_user = nil @proxy_pass = nil + @proxy_use_ssl = nil @use_ssl = false @ssl_context = nil @@ -1233,7 +1304,7 @@ module Net #:nodoc: # Sets whether to determine the proxy from environment variable # '<tt>ENV['http_proxy']</tt>'; - # see {Proxy Using ENV['http_proxy']}[rdoc-ref:Net::HTTP@Proxy+Using+-27ENV-5B-27http_proxy-27-5D-27]. + # see {Proxy Using ENV['http_proxy']}[rdoc-ref:Net::HTTP@Proxy+Using+ENVHTTPProxy]. attr_writer :proxy_from_env # Sets the proxy address; @@ -1252,6 +1323,10 @@ module Net #:nodoc: # see {Proxy Server}[rdoc-ref:Net::HTTP@Proxy+Server]. attr_writer :proxy_pass + # Sets whether the proxy uses SSL; + # see {Proxy Server}[rdoc-ref:Net::HTTP@Proxy+Server]. + attr_writer :proxy_use_ssl + # Returns the IP address for the connection. # # If the session has not been started, @@ -1440,23 +1515,6 @@ module Net #:nodoc: @use_ssl = flag end - SSL_IVNAMES = [ - :@ca_file, - :@ca_path, - :@cert, - :@cert_store, - :@ciphers, - :@extra_chain_cert, - :@key, - :@ssl_timeout, - :@ssl_version, - :@min_version, - :@max_version, - :@verify_callback, - :@verify_depth, - :@verify_mode, - :@verify_hostname, - ] # :nodoc: SSL_ATTRIBUTES = [ :ca_file, :ca_path, @@ -1473,7 +1531,9 @@ module Net #:nodoc: :verify_depth, :verify_mode, :verify_hostname, - ] # :nodoc: + ].freeze # :nodoc: + + SSL_IVNAMES = SSL_ATTRIBUTES.map { |a| "@#{a}".to_sym }.freeze # :nodoc: # Sets or returns the path to a CA certification file in PEM format. attr_accessor :ca_file @@ -1490,11 +1550,11 @@ module Net #:nodoc: attr_accessor :cert_store # Sets or returns the available SSL ciphers. - # See {OpenSSL::SSL::SSLContext#ciphers=}[rdoc-ref:OpenSSL::SSL::SSLContext#ciphers-3D]. + # See {OpenSSL::SSL::SSLContext#ciphers=}[OpenSSL::SSL::SSL::Context#ciphers=]. attr_accessor :ciphers # Sets or returns the extra X509 certificates to be added to the certificate chain. - # See {OpenSSL::SSL::SSLContext#add_certificate}[rdoc-ref:OpenSSL::SSL::SSLContext#add_certificate]. + # See {OpenSSL::SSL::SSLContext#add_certificate}[OpenSSL::SSL::SSL::Context#add_certificate]. attr_accessor :extra_chain_cert # Sets or returns the OpenSSL::PKey::RSA or OpenSSL::PKey::DSA object. @@ -1504,15 +1564,15 @@ module Net #:nodoc: attr_accessor :ssl_timeout # Sets or returns the SSL version. - # See {OpenSSL::SSL::SSLContext#ssl_version=}[rdoc-ref:OpenSSL::SSL::SSLContext#ssl_version-3D]. + # See {OpenSSL::SSL::SSLContext#ssl_version=}[OpenSSL::SSL::SSL::Context#ssl_version=]. attr_accessor :ssl_version # Sets or returns the minimum SSL version. - # See {OpenSSL::SSL::SSLContext#min_version=}[rdoc-ref:OpenSSL::SSL::SSLContext#min_version-3D]. + # See {OpenSSL::SSL::SSLContext#min_version=}[OpenSSL::SSL::SSL::Context#min_version=]. attr_accessor :min_version # Sets or returns the maximum SSL version. - # See {OpenSSL::SSL::SSLContext#max_version=}[rdoc-ref:OpenSSL::SSL::SSLContext#max_version-3D]. + # See {OpenSSL::SSL::SSLContext#max_version=}[OpenSSL::SSL::SSL::Context#max_version=]. attr_accessor :max_version # Sets or returns the callback for the server certification verification. @@ -1528,7 +1588,7 @@ module Net #:nodoc: # Sets or returns whether to verify that the server certificate is valid # for the hostname. - # See {OpenSSL::SSL::SSLContext#verify_hostname=}[rdoc-ref:OpenSSL::SSL::SSLContext#attribute-i-verify_mode]. + # See {OpenSSL::SSL::SSLContext#verify_hostname=}[OpenSSL::SSL::SSL::Context#verify_hostname=]. attr_accessor :verify_hostname # Returns the X509 certificate chain (an array of strings) @@ -1576,6 +1636,21 @@ module Net #:nodoc: self end + # Finishes the \HTTP session: + # + # http = Net::HTTP.new(hostname) + # http.start + # http.started? # => true + # http.finish # => nil + # http.started? # => false + # + # Raises IOError if not in a session. + def finish + raise IOError, 'HTTP session not yet started' unless started? + do_finish + end + + # :stopdoc: def do_start connect @started = true @@ -1598,19 +1673,26 @@ module Net #:nodoc: end debug "opening connection to #{conn_addr}:#{conn_port}..." - s = Timeout.timeout(@open_timeout, Net::OpenTimeout) { - begin - TCPSocket.open(conn_addr, conn_port, @local_host, @local_port) - rescue => e - raise e, "Failed to open TCP connection to " + - "#{conn_addr}:#{conn_port} (#{e.message})" + begin + s = timeouted_connect(conn_addr, conn_port) + rescue => e + if (defined?(IO::TimeoutError) && e.is_a?(IO::TimeoutError)) || e.is_a?(Errno::ETIMEDOUT) # for compatibility with previous versions + e = Net::OpenTimeout.new(e) end - } + raise e, "Failed to open TCP connection to " + + "#{conn_addr}:#{conn_port} (#{e.message})" + end s.setsockopt(Socket::IPPROTO_TCP, Socket::TCP_NODELAY, 1) debug "opened" if use_ssl? if proxy? - plain_sock = BufferedIO.new(s, read_timeout: @read_timeout, + if @proxy_use_ssl + proxy_sock = OpenSSL::SSL::SSLSocket.new(s) + ssl_socket_connect(proxy_sock, @open_timeout) + else + proxy_sock = s + end + proxy_sock = BufferedIO.new(proxy_sock, read_timeout: @read_timeout, write_timeout: @write_timeout, continue_timeout: @continue_timeout, debug_output: @debug_output) @@ -1621,8 +1703,8 @@ module Net #:nodoc: buf << "Proxy-Authorization: Basic #{credential}\r\n" end buf << "\r\n" - plain_sock.write(buf) - HTTPResponse.read_new(plain_sock).value + proxy_sock.write(buf) + HTTPResponse.read_new(proxy_sock).value # assuming nothing left in buffers after successful CONNECT response end @@ -1692,23 +1774,30 @@ module Net #:nodoc: end private :connect - def on_connect + tcp_socket_parameters = TCPSocket.instance_method(:initialize).parameters + TCP_SOCKET_NEW_HAS_OPEN_TIMEOUT = if tcp_socket_parameters != [[:rest]] + tcp_socket_parameters.include?([:key, :open_timeout]) + else + # Use Socket.tcp to find out since there is no parameters information for TCPSocket#initialize + # See discussion in https://github.com/ruby/net-http/pull/224 + Socket.method(:tcp).parameters.include?([:key, :open_timeout]) end - private :on_connect + private_constant :TCP_SOCKET_NEW_HAS_OPEN_TIMEOUT - # Finishes the \HTTP session: - # - # http = Net::HTTP.new(hostname) - # http.start - # http.started? # => true - # http.finish # => nil - # http.started? # => false - # - # Raises IOError if not in a session. - def finish - raise IOError, 'HTTP session not yet started' unless started? - do_finish + def timeouted_connect(conn_addr, conn_port) + if TCP_SOCKET_NEW_HAS_OPEN_TIMEOUT + TCPSocket.open(conn_addr, conn_port, @local_host, @local_port, open_timeout: @open_timeout) + else + Timeout.timeout(@open_timeout, Net::OpenTimeout) { + TCPSocket.open(conn_addr, conn_port, @local_host, @local_port) + } + end + end + private :timeouted_connect + + def on_connect end + private :on_connect def do_finish @started = false @@ -1730,13 +1819,14 @@ module Net #:nodoc: @proxy_port = nil @proxy_user = nil @proxy_pass = nil + @proxy_use_ssl = nil # Creates an \HTTP proxy class which behaves like \Net::HTTP, but # performs all access via the specified proxy. # # This class is obsolete. You may pass these same parameters directly to # \Net::HTTP.new. See Net::HTTP.new for details of the arguments. - def HTTP.Proxy(p_addr = :ENV, p_port = nil, p_user = nil, p_pass = nil) #:nodoc: + def HTTP.Proxy(p_addr = :ENV, p_port = nil, p_user = nil, p_pass = nil, p_use_ssl = nil) #:nodoc: return self unless p_addr Class.new(self) { @@ -1754,9 +1844,12 @@ module Net #:nodoc: @proxy_user = p_user @proxy_pass = p_pass + @proxy_use_ssl = p_use_ssl } end + # :startdoc: + class << HTTP # Returns true if self is a class which was created by HTTP::Proxy. def proxy_class? @@ -1778,6 +1871,9 @@ module Net #:nodoc: # Returns the password for accessing the proxy, or +nil+ if none; # see Net::HTTP@Proxy+Server. attr_reader :proxy_pass + + # Use SSL when talking to the proxy. If Net::HTTP does not use a proxy, nil. + attr_reader :proxy_use_ssl end # Returns +true+ if a proxy server is defined, +false+ otherwise; @@ -1848,9 +1944,11 @@ module Net #:nodoc: alias proxyport proxy_port #:nodoc: obsolete private + # :stopdoc: def unescape(value) - require 'cgi/util' + require 'cgi/escape' + require 'cgi/util' unless defined?(CGI::EscapeExt) CGI.unescape(value) end @@ -1875,6 +1973,7 @@ module Net #:nodoc: path end end + # :startdoc: # # HTTP operations @@ -2012,6 +2111,11 @@ module Net #:nodoc: # http = Net::HTTP.new(hostname) # http.put('/todos/1', data) # => #<Net::HTTPOK 200 OK readbody=true> # + # Related: + # + # - Net::HTTP::Put: request class for \HTTP method PUT. + # - Net::HTTP.put: sends PUT request, returns response body. + # def put(path, data, initheader = nil) request(Put.new(path, initheader), data) end @@ -2324,7 +2428,9 @@ module Net #:nodoc: res end - IDEMPOTENT_METHODS_ = %w/GET HEAD PUT DELETE OPTIONS TRACE/ # :nodoc: + # :stopdoc: + + IDEMPOTENT_METHODS_ = %w/GET HEAD PUT DELETE OPTIONS TRACE/.freeze # :nodoc: def transport_request(req) count = 0 @@ -2350,7 +2456,10 @@ module Net #:nodoc: res } res.reading_body(@socket, req.response_body_permitted?) { - yield res if block_given? + if block_given? + count = max_retries # Don't restart in the middle of a download + yield res + end } rescue Net::OpenTimeout raise @@ -2478,6 +2587,11 @@ module Net #:nodoc: alias_method :D, :debug end + # for backward compatibility until Ruby 4.0 + # https://bugs.ruby-lang.org/issues/20900 + # https://github.com/bblimke/webmock/pull/1081 + HTTPSession = HTTP + deprecate_constant :HTTPSession end require_relative 'http/exceptions' @@ -2492,5 +2606,3 @@ require_relative 'http/response' require_relative 'http/responses' require_relative 'http/proxy_delta' - -require_relative 'http/backward' |
