diff --git a/.travis.yml b/.travis.yml new file mode 100644 index 0000000000000000000000000000000000000000..21ab797b32cf9f0a1b6c5d7f585aeef4d4457ffe --- /dev/null +++ b/.travis.yml @@ -0,0 +1,15 @@ +--- +after_script: +- rake travis:after -t +before_script: +- gem install hoe-travis --no-rdoc --no-ri +- rake travis:before -t +language: ruby +notifications: + email: + - drbrain@segment7.net +rvm: +- 2.1.10 +- 2.2.5 +- 2.3.1 +script: rake travis diff --git a/History.txt b/History.txt index e16c69a9be7d770bb50c0970956cdb9e3ccc2a96..7cff8904c77845ff414adb6b926a325cdb8dd2e4 100644 --- a/History.txt +++ b/History.txt @@ -1,3 +1,22 @@ +=== 3.0 + +Breaking changes: + +* No longer supports ruby 2.0 and earlier +* Net::HTTP::Persistent::new now uses keyword arguments for +name+ and + +proxy+. +* Removed #max_age, use #expired? + +New features: + +* Uses connection_pool to manage all connections for a Net::HTTP::Persistent + instance. + +Bug fixes: + +* Add missing SSL options ca_path, ciphers, ssl_timeout, verify_depth. + Issue #63 by Johnneylee Jack Rollins. + === 2.9.4 / 2014-02-10 * Bug fixes diff --git a/Manifest.txt b/Manifest.txt index bc4063f773c78e502cb248b659c4cb7655c43124..e94aa627a8bb53c0d8e900213a62b22a4c223445 100644 --- a/Manifest.txt +++ b/Manifest.txt @@ -1,11 +1,13 @@ .autotest .gemtest +.travis.yml History.txt Manifest.txt README.rdoc Rakefile -lib/net/http/faster.rb lib/net/http/persistent.rb -lib/net/http/persistent/ssl_reuse.rb +lib/net/http/persistent/connection.rb +lib/net/http/persistent/pool.rb +lib/net/http/persistent/timed_stack_multi.rb test/test_net_http_persistent.rb -test/test_net_http_persistent_ssl_reuse.rb +test/test_net_http_persistent_timed_stack_multi.rb diff --git a/Rakefile b/Rakefile index 9dfd9ba3c19711a3e3a5b834ec2aab445d93973d..499d4651febdb6a6526b7ba497193048310a06ff 100644 --- a/Rakefile +++ b/Rakefile @@ -13,12 +13,15 @@ Hoe.spec 'net-http-persistent' do self.readme_file = 'README.rdoc' self.extra_rdoc_files += Dir['*.rdoc'] + self.require_ruby_version '~> 2.1' + license 'MIT' rdoc_locations << 'docs.seattlerb.org:/data/www/docs.seattlerb.org/net-http-persistent/' - dependency 'minitest', '~> 5.2', :development + dependency 'connection_pool', '~> 2.2' + dependency 'minitest', '~> 5.2', :development end # vim: syntax=Ruby diff --git a/checksums.yaml.gz b/checksums.yaml.gz deleted file mode 100644 index abf74fa0cdbf95e427d6ea8d0d6d857e5d83f1ea..0000000000000000000000000000000000000000 Binary files a/checksums.yaml.gz and /dev/null differ diff --git a/checksums.yaml.gz.sig b/checksums.yaml.gz.sig index 2c41b0758ed6eca39c0298d005764cddce8f974f..b4adf6ed57ea5b9de34de7d6691f161acec3e9e7 100644 Binary files a/checksums.yaml.gz.sig and b/checksums.yaml.gz.sig differ diff --git a/data.tar.gz.sig b/data.tar.gz.sig index a93de4860225df3bdf99294a6938db125a1e76b5..01ac4e1cca19dbeb8f5debe5dd3795110e67425b 100644 Binary files a/data.tar.gz.sig and b/data.tar.gz.sig differ diff --git a/lib/net/http/faster.rb b/lib/net/http/faster.rb deleted file mode 100644 index e5e09080c27a45d84afa15d9bd0cbba9abcf5328..0000000000000000000000000000000000000000 --- a/lib/net/http/faster.rb +++ /dev/null @@ -1,27 +0,0 @@ -require 'net/protocol' - -## -# Aaron Patterson's monkeypatch (accepted into 1.9.1) to fix Net::HTTP's speed -# problems. -# -# http://gist.github.com/251244 - -class Net::BufferedIO #:nodoc: - alias :old_rbuf_fill :rbuf_fill - - def rbuf_fill - if @io.respond_to? :read_nonblock then - begin - @rbuf << @io.read_nonblock(65536) - rescue Errno::EWOULDBLOCK, Errno::EAGAIN => e - retry if IO.select [@io], nil, nil, @read_timeout - raise Timeout::Error, e.message - end - else # SSL sockets do not have read_nonblock - timeout @read_timeout do - @rbuf << @io.sysread(65536) - end - end - end -end if RUBY_VERSION < '1.9' - diff --git a/lib/net/http/persistent.rb b/lib/net/http/persistent.rb index 98e149004117f92e41390040e4c3fb2a5be18e1e..e6a110cd9db290a9a18d4c9c0f9dc6db3013b409 100644 --- a/lib/net/http/persistent.rb +++ b/lib/net/http/persistent.rb @@ -1,12 +1,7 @@ require 'net/http' -begin - require 'net/https' -rescue LoadError - # net/https or openssl -end if RUBY_VERSION < '1.9' # but only for 1.8 -require 'net/http/faster' require 'uri' require 'cgi' # for escaping +require 'connection_pool' begin require 'net/http/pipeline' @@ -70,13 +65,17 @@ autoload :OpenSSL, 'openssl' # Here are the SSL settings, see the individual methods for documentation: # # #certificate :: This client's certificate -# #ca_file :: The certificate-authority +# #ca_file :: The certificate-authorities +# #ca_path :: Directory with certificate-authorities # #cert_store :: An SSL certificate store +# #ciphers :: List of SSl ciphers allowed # #private_key :: The client's SSL private key # #reuse_ssl_sessions :: Reuse a previously opened SSL session for a new # connection +# #ssl_timeout :: SSL session lifetime # #ssl_version :: Which specific SSL version to use # #verify_callback :: For server certificate verification +# #verify_depth :: Depth of certificate verification # #verify_mode :: How connections should be verified # # == Proxies @@ -200,10 +199,15 @@ class Net::HTTP::Persistent HAVE_OPENSSL = defined? OpenSSL::SSL # :nodoc: + ## + # The default connection pool size is 1/4 the allowed open files. + + DEFAULT_POOL_SIZE = Process.getrlimit(Process::RLIMIT_NOFILE).first / 4 + ## # The version of Net::HTTP::Persistent you are using - VERSION = '2.9.4' + VERSION = '3.0.0' ## # Exceptions rescued for automatic retry on ruby 2.0.0. This overlaps with @@ -248,31 +252,31 @@ class Net::HTTP::Persistent http = new 'net-http-persistent detect_idle_timeout' - connection = http.connection_for uri + http.connection_for uri do |connection| + sleep_time = 0 - sleep_time = 0 + http = connection.http - loop do - response = connection.request req + loop do + response = http.request req - $stderr.puts "HEAD #{uri} => #{response.code}" if $DEBUG + $stderr.puts "HEAD #{uri} => #{response.code}" if $DEBUG - unless Net::HTTPOK === response then - raise Error, "bad response code #{response.code} detecting idle timeout" - end + unless Net::HTTPOK === response then + raise Error, "bad response code #{response.code} detecting idle timeout" + end - break if sleep_time >= max + break if sleep_time >= max - sleep_time += 1 + sleep_time += 1 - $stderr.puts "sleeping #{sleep_time}" if $DEBUG - sleep sleep_time + $stderr.puts "sleeping #{sleep_time}" if $DEBUG + sleep sleep_time + end end rescue # ignore StandardErrors, we've probably found the idle timeout. ensure - http.shutdown - return sleep_time unless $! end @@ -281,7 +285,9 @@ class Net::HTTP::Persistent attr_reader :certificate + ## # For Net::HTTP parity + alias cert certificate ## @@ -290,12 +296,23 @@ class Net::HTTP::Persistent attr_reader :ca_file + ## + # A directory of SSL certificates to be used as certificate authorities. + # Setting this will set verify_mode to VERIFY_PEER. + + attr_reader :ca_path + ## # An SSL certificate store. Setting this will override the default # certificate store. See verify_mode for more information. attr_reader :cert_store + ## + # The ciphers allowed for SSL connections + + attr_reader :ciphers + ## # Sends debug_output to this IO via Net::HTTP#set_debug_output. # @@ -309,11 +326,6 @@ class Net::HTTP::Persistent attr_reader :generation # :nodoc: - ## - # Where this instance's connections live in the thread local variables - - attr_reader :generation_key # :nodoc: - ## # Headers that are added to every request using Net::HTTP#add_field @@ -369,7 +381,9 @@ class Net::HTTP::Persistent attr_reader :private_key + ## # For Net::HTTP parity + alias key private_key ## @@ -383,14 +397,14 @@ class Net::HTTP::Persistent attr_reader :no_proxy ## - # Seconds to wait until reading one block. See Net::HTTP#read_timeout + # Test-only accessor for the connection pool - attr_accessor :read_timeout + attr_reader :pool # :nodoc: ## - # Where this instance's request counts live in the thread local variables + # Seconds to wait until reading one block. See Net::HTTP#read_timeout - attr_reader :request_key # :nodoc: + attr_accessor :read_timeout ## # By default SSL sessions are reused to avoid extra SSL handshakes. Set @@ -418,9 +432,9 @@ class Net::HTTP::Persistent attr_reader :ssl_generation # :nodoc: ## - # Where this instance's SSL connections live in the thread local variables + # SSL session lifetime - attr_reader :ssl_generation_key # :nodoc: + attr_reader :ssl_timeout ## # SSL version to use. @@ -428,7 +442,7 @@ class Net::HTTP::Persistent # By default, the version will be negotiated automatically between client # and server. Ruby 1.9 and newer only. - attr_reader :ssl_version if RUBY_VERSION > '1.9' + attr_reader :ssl_version ## # Where this instance's last-use times live in the thread local variables @@ -436,16 +450,21 @@ class Net::HTTP::Persistent attr_reader :timeout_key # :nodoc: ## - # SSL verification callback. Used when ca_file is set. + # SSL verification callback. Used when ca_file or ca_path is set. attr_reader :verify_callback + ## + # Sets the depth of SSL certificate verification + + attr_reader :verify_depth + ## # HTTPS verify mode. Defaults to OpenSSL::SSL::VERIFY_PEER which verifies # the server certificate. # - # If no ca_file or cert_store is set the default system certificate store is - # used. + # If no ca_file, ca_path or cert_store is set the default system certificate + # store is used. # # You can use +verify_mode+ to override any default values. @@ -478,8 +497,12 @@ class Net::HTTP::Persistent # proxy = URI 'http://proxy.example' # proxy.user = 'AzureDiamond' # proxy.password = 'hunter2' + # + # Set +pool_size+ to limit the maximum number of connections allowed. + # Defaults to 1/4 the number of allowed file handles. You can have no more + # than this many threads with active HTTP transactions. - def initialize name = nil, proxy = nil + def initialize name: nil, proxy: nil, pool_size: DEFAULT_POOL_SIZE @name = name @debug_output = nil @@ -494,26 +517,28 @@ class Net::HTTP::Persistent @idle_timeout = 5 @max_requests = nil @socket_options = [] + @ssl_generation = 0 # incremented when SSL session variables change @socket_options << [Socket::IPPROTO_TCP, Socket::TCP_NODELAY, 1] if Socket.const_defined? :TCP_NODELAY - key = ['net_http_persistent', name].compact - @generation_key = [key, 'generations' ].join('_').intern - @ssl_generation_key = [key, 'ssl_generations'].join('_').intern - @request_key = [key, 'requests' ].join('_').intern - @timeout_key = [key, 'timeouts' ].join('_').intern + @pool = Net::HTTP::Persistent::Pool.new size: pool_size do |http_args| + Net::HTTP::Persistent::Connection.new Net::HTTP, http_args, @ssl_generation + end @certificate = nil @ca_file = nil + @ca_path = nil + @ciphers = nil @private_key = nil + @ssl_timeout = nil @ssl_version = nil @verify_callback = nil + @verify_depth = nil @verify_mode = nil @cert_store = nil @generation = 0 # incremented when proxy URI changes - @ssl_generation = 0 # incremented when SSL session variables change if HAVE_OPENSSL then @verify_mode = OpenSSL::SSL::VERIFY_PEER @@ -522,9 +547,6 @@ class Net::HTTP::Persistent @retry_change_requests = false - @ruby_1 = RUBY_VERSION < '2' - @retried_on_ruby_2 = !@ruby_1 - self.proxy = proxy if proxy end @@ -549,6 +571,15 @@ class Net::HTTP::Persistent reconnect_ssl end + ## + # Sets the SSL certificate authority path. + + def ca_path= path + @ca_path = path + + reconnect_ssl + end + ## # Overrides the default SSL certificate store used for verifying # connections. @@ -560,90 +591,55 @@ class Net::HTTP::Persistent end ## - # Finishes all connections on the given +thread+ that were created before - # the given +generation+ in the threads +generation_key+ list. - # - # See #shutdown for a bunch of scary warning about misusing this method. - - def cleanup(generation, thread = Thread.current, - generation_key = @generation_key) # :nodoc: - timeouts = thread[@timeout_key] + # The ciphers allowed for SSL connections - (0...generation).each do |old_generation| - next unless thread[generation_key] + def ciphers= ciphers + @ciphers = ciphers - conns = thread[generation_key].delete old_generation - - conns.each_value do |conn| - finish conn, thread - - timeouts.delete conn.object_id if timeouts - end if conns - end + reconnect_ssl end ## # Creates a new connection for +uri+ def connection_for uri - Thread.current[@generation_key] ||= Hash.new { |h,k| h[k] = {} } - Thread.current[@ssl_generation_key] ||= Hash.new { |h,k| h[k] = {} } - Thread.current[@request_key] ||= Hash.new 0 - Thread.current[@timeout_key] ||= Hash.new EPOCH - use_ssl = uri.scheme.downcase == 'https' - if use_ssl then - raise Net::HTTP::Persistent::Error, 'OpenSSL is not available' unless - HAVE_OPENSSL - - ssl_generation = @ssl_generation - - ssl_cleanup ssl_generation + net_http_args = [uri.host, uri.port] - connections = Thread.current[@ssl_generation_key][ssl_generation] - else - generation = @generation + net_http_args.concat @proxy_args if + @proxy_uri and not proxy_bypass? uri.host, uri.port - cleanup generation + connection = @pool.checkout net_http_args - connections = Thread.current[@generation_key][generation] - end + http = connection.http - net_http_args = [uri.host, uri.port] - connection_id = net_http_args.join ':' + connection.ressl @ssl_generation if + connection.ssl_generation != @ssl_generation - if @proxy_uri and not proxy_bypass? uri.host, uri.port then - connection_id << @proxy_connection_id - net_http_args.concat @proxy_args - end - - connection = connections[connection_id] - - unless connection = connections[connection_id] then - connections[connection_id] = http_class.new(*net_http_args) - connection = connections[connection_id] - ssl connection if use_ssl - else - reset connection if expired? connection + if not http.started? then + ssl http if use_ssl + start http + elsif expired? connection then + reset connection end - start connection unless connection.started? - - connection.read_timeout = @read_timeout if @read_timeout - connection.keep_alive_timeout = @idle_timeout if @idle_timeout && connection.respond_to?(:keep_alive_timeout=) + http.read_timeout = @read_timeout if @read_timeout + http.keep_alive_timeout = @idle_timeout if @idle_timeout - connection + return yield connection rescue Errno::ECONNREFUSED - address = connection.proxy_address || connection.address - port = connection.proxy_port || connection.port + address = http.proxy_address || http.address + port = http.proxy_port || http.port raise Error, "connection refused: #{address}:#{port}" rescue Errno::EHOSTDOWN - address = connection.proxy_address || connection.address - port = connection.proxy_port || connection.port + address = http.proxy_address || http.address + port = http.proxy_port || http.port raise Error, "host down: #{address}:#{port}" + ensure + @pool.checkin net_http_args end ## @@ -651,12 +647,11 @@ class Net::HTTP::Persistent # this connection def error_message connection - requests = Thread.current[@request_key][connection.object_id] - 1 # fixup - last_use = Thread.current[@timeout_key][connection.object_id] + connection.requests -= 1 # fixup - age = Time.now - last_use + age = Time.now - connection.last_use - "after #{requests} requests on #{connection.object_id}, " \ + "after #{connection.requests} requests on #{connection.http.object_id}, " \ "last used #{age} seconds ago" end @@ -680,26 +675,23 @@ class Net::HTTP::Persistent # maximum request count, false otherwise. def expired? connection - requests = Thread.current[@request_key][connection.object_id] - return true if @max_requests && requests >= @max_requests + return true if @max_requests && connection.requests >= @max_requests return false unless @idle_timeout return true if @idle_timeout.zero? - last_used = Thread.current[@timeout_key][connection.object_id] - - Time.now - last_used > @idle_timeout + Time.now - connection.last_use > @idle_timeout end ## # Starts the Net::HTTP +connection+ - def start connection - connection.set_debug_output @debug_output if @debug_output - connection.open_timeout = @open_timeout if @open_timeout + def start http + http.set_debug_output @debug_output if @debug_output + http.open_timeout = @open_timeout if @open_timeout - connection.start + http.start - socket = connection.instance_variable_get :@socket + socket = http.instance_variable_get :@socket if socket then # for fakeweb @socket_options.each do |option| @@ -711,25 +703,11 @@ class Net::HTTP::Persistent ## # Finishes the Net::HTTP +connection+ - def finish connection, thread = Thread.current - if requests = thread[@request_key] then - requests.delete connection.object_id - end - + def finish connection connection.finish - rescue IOError - end - def http_class # :nodoc: - if RUBY_VERSION > '2.0' then - Net::HTTP - elsif [:Artifice, :FakeWeb, :WebMock].any? { |klass| - Object.const_defined?(klass) - } or not @reuse_ssl_sessions then - Net::HTTP - else - Net::HTTP::Persistent::SSLReuse - end + connection.http.instance_variable_set :@ssl_session, nil unless + @reuse_ssl_sessions end ## @@ -752,55 +730,9 @@ class Net::HTTP::Persistent ## # Is the request +req+ idempotent or is retry_change_requests allowed. - # - # If +retried_on_ruby_2+ is true, true will be returned if we are on ruby, - # retry_change_requests is allowed and the request is not idempotent. - - def can_retry? req, retried_on_ruby_2 = false - return @retry_change_requests && !idempotent?(req) if retried_on_ruby_2 - @retry_change_requests || idempotent?(req) - end - - if RUBY_VERSION > '1.9' then - ## - # Workaround for missing Net::HTTPHeader#connection_close? on Ruby 1.8 - - def connection_close? header - header.connection_close? - end - - ## - # Workaround for missing Net::HTTPHeader#connection_keep_alive? on Ruby 1.8 - - def connection_keep_alive? header - header.connection_keep_alive? - end - else - ## - # Workaround for missing Net::HTTPRequest#connection_close? on Ruby 1.8 - - def connection_close? header - header['connection'] =~ /close/ or header['proxy-connection'] =~ /close/ - end - - ## - # Workaround for missing Net::HTTPRequest#connection_keep_alive? on Ruby - # 1.8 - - def connection_keep_alive? header - header['connection'] =~ /keep-alive/ or - header['proxy-connection'] =~ /keep-alive/ - end - end - - ## - # Deprecated in favor of #expired? - - def max_age # :nodoc: - return Time.now + 1 unless @idle_timeout - - Time.now - @idle_timeout + def can_retry? req + @retry_change_requests && !idempotent?(req) end ## @@ -822,9 +754,9 @@ class Net::HTTP::Persistent # <tt>net-http-persistent</tt> #pipeline will be present. def pipeline uri, requests, &block # :yields: responses - connection = connection_for uri - - connection.pipeline requests, &block + connection_for uri do |connection| + connection.http.pipeline requests, &block + end end ## @@ -957,18 +889,17 @@ class Net::HTTP::Persistent # Finishes then restarts the Net::HTTP +connection+ def reset connection - Thread.current[@request_key].delete connection.object_id - Thread.current[@timeout_key].delete connection.object_id + http = connection.http finish connection - start connection + start http rescue Errno::ECONNREFUSED - e = Error.new "connection refused: #{connection.address}:#{connection.port}" + e = Error.new "connection refused: #{http.address}:#{http.port}" e.set_backtrace $@ raise e rescue Errno::EHOSTDOWN - e = Error.new "host down: #{connection.address}:#{connection.port}" + e = Error.new "host down: #{http.address}:#{http.port}" e.set_backtrace $@ raise e end @@ -989,52 +920,56 @@ class Net::HTTP::Persistent retried = false bad_response = false - req = request_setup req || uri + uri = URI uri + req = request_setup req || uri + response = nil - connection = connection_for uri - connection_id = connection.object_id + connection_for uri do |connection| + http = connection.http - begin - Thread.current[@request_key][connection_id] += 1 - response = connection.request req, &block + begin + connection.requests += 1 - if connection_close?(req) or - (response.http_version <= '1.0' and - not connection_keep_alive?(response)) or - connection_close?(response) then - connection.finish - end - rescue Net::HTTPBadResponse => e - message = error_message connection + response = http.request req, &block - finish connection + if req.connection_close? or + (response.http_version <= '1.0' and + not response.connection_keep_alive?) or + response.connection_close? then + finish connection + end + rescue Net::HTTPBadResponse => e + message = error_message connection - raise Error, "too many bad responses #{message}" if + finish connection + + raise Error, "too many bad responses #{message}" if bad_response or not can_retry? req - bad_response = true - retry - rescue *RETRIED_EXCEPTIONS => e # retried on ruby 2 - request_failed e, req, connection if - retried or not can_retry? req, @retried_on_ruby_2 + bad_response = true + retry + rescue *RETRIED_EXCEPTIONS => e + request_failed e, req, connection if + retried or not can_retry? req - reset connection + reset connection - retried = true - retry - rescue Errno::EINVAL, Errno::ETIMEDOUT => e # not retried on ruby 2 - request_failed e, req, connection if retried or not can_retry? req + retried = true + retry + rescue Errno::EINVAL, Errno::ETIMEDOUT => e # not retried on ruby 2 + request_failed e, req, connection if retried or not can_retry? req - reset connection + reset connection - retried = true - retry - rescue Exception => e - finish connection + retried = true + retry + rescue Exception => e + finish connection - raise - ensure - Thread.current[@timeout_key][connection_id] = Time.now + raise + ensure + connection.last_use = Time.now + end end @http_versions["#{uri.host}:#{uri.port}"] ||= response.http_version @@ -1054,7 +989,6 @@ class Net::HTTP::Persistent finish connection - raise Error, message, exception.backtrace end @@ -1088,45 +1022,17 @@ class Net::HTTP::Persistent end ## - # Shuts down all connections for +thread+. - # - # Uses the current thread by default. - # - # If you've used Net::HTTP::Persistent across multiple threads you should - # call this in each thread when you're done making HTTP requests. + # Shuts down all connections # - # *NOTE*: Calling shutdown for another thread can be dangerous! + # *NOTE*: Calling shutdown for can be dangerous! # - # If the thread is still using the connection it may cause an error! It is - # best to call #shutdown in the thread at the appropriate time instead! + # If any thread is still using a connection it may cause an error! Call + # #shutdown when you are completely done making requests! - def shutdown thread = Thread.current - generation = reconnect - cleanup generation, thread, @generation_key - - ssl_generation = reconnect_ssl - cleanup ssl_generation, thread, @ssl_generation_key - - thread[@request_key] = nil - thread[@timeout_key] = nil - end - - ## - # Shuts down all connections in all threads - # - # *NOTE*: THIS METHOD IS VERY DANGEROUS! - # - # Do not call this method if other threads are still using their - # connections! Call #shutdown at the appropriate time instead! - # - # Use this method only as a last resort! - - def shutdown_in_all_threads - Thread.list.each do |thread| - shutdown thread + def shutdown + @pool.available.shutdown do |http| + http.finish end - - nil end ## @@ -1135,9 +1041,12 @@ class Net::HTTP::Persistent def ssl connection connection.use_ssl = true + connection.ciphers = @ciphers if @ciphers + connection.ssl_timeout = @ssl_timeout if @ssl_timeout connection.ssl_version = @ssl_version if @ssl_version - connection.verify_mode = @verify_mode + connection.verify_depth = @verify_depth + connection.verify_mode = @verify_mode if OpenSSL::SSL::VERIFY_PEER == OpenSSL::SSL::VERIFY_NONE and not Object.const_defined?(:I_KNOW_THAT_OPENSSL_VERIFY_PEER_EQUALS_VERIFY_NONE_IS_WRONG) then @@ -1166,8 +1075,10 @@ application: WARNING end - if @ca_file then - connection.ca_file = @ca_file + connection.ca_file = @ca_file if @ca_file + connection.ca_path = @ca_path if @ca_path + + if @ca_file or @ca_path then connection.verify_mode = OpenSSL::SSL::VERIFY_PEER connection.verify_callback = @verify_callback if @verify_callback end @@ -1187,11 +1098,12 @@ application: end ## - # Finishes all connections that existed before the given SSL parameter - # +generation+. + # SSL session lifetime - def ssl_cleanup generation # :nodoc: - cleanup generation, Thread.current, @ssl_generation_key + def ssl_timeout= ssl_timeout + @ssl_timeout = ssl_timeout + + reconnect_ssl end ## @@ -1201,7 +1113,16 @@ application: @ssl_version = ssl_version reconnect_ssl - end if RUBY_VERSION > '1.9' + end + + ## + # Sets the depth of SSL certificate verification + + def verify_depth= verify_depth + @verify_depth = verify_depth + + reconnect_ssl + end ## # Sets the HTTPS verify mode. Defaults to OpenSSL::SSL::VERIFY_PEER. @@ -1227,5 +1148,6 @@ application: end -require 'net/http/persistent/ssl_reuse' +require 'net/http/persistent/connection' +require 'net/http/persistent/pool' diff --git a/lib/net/http/persistent/connection.rb b/lib/net/http/persistent/connection.rb new file mode 100644 index 0000000000000000000000000000000000000000..4e1c6018af0a5fa0059bc8d2c571f87dc2b8bb6f --- /dev/null +++ b/lib/net/http/persistent/connection.rb @@ -0,0 +1,40 @@ +## +# A Net::HTTP connection wrapper that holds extra information for managing the +# connection's lifetime. + +class Net::HTTP::Persistent::Connection # :nodoc: + + attr_accessor :http + + attr_accessor :last_use + + attr_accessor :requests + + attr_accessor :ssl_generation + + def initialize http_class, http_args, ssl_generation + @http = http_class.new(*http_args) + @ssl_generation = ssl_generation + + reset + end + + def finish + @http.finish + rescue IOError + ensure + reset + end + + def reset + @last_use = Net::HTTP::Persistent::EPOCH + @requests = 0 + end + + def ressl ssl_generation + @ssl_generation = ssl_generation + + finish + end + +end diff --git a/lib/net/http/persistent/pool.rb b/lib/net/http/persistent/pool.rb new file mode 100644 index 0000000000000000000000000000000000000000..b88dd592a43ccf7877691dc556e96d34406278ea --- /dev/null +++ b/lib/net/http/persistent/pool.rb @@ -0,0 +1,46 @@ +class Net::HTTP::Persistent::Pool < ConnectionPool # :nodoc: + + attr_reader :available # :nodoc: + attr_reader :key # :nodoc: + + def initialize(options = {}, &block) + super + + @available = Net::HTTP::Persistent::TimedStackMulti.new(@size, &block) + @key = :"current-#{@available.object_id}" + end + + def checkin net_http_args + stack = Thread.current[@key][net_http_args] + + raise ConnectionPool::Error, 'no connections are checked out' if + stack.empty? + + conn = stack.pop + + if stack.empty? + @available.push conn, connection_args: net_http_args + end + + nil + end + + def checkout net_http_args + stacks = Thread.current[@key] ||= Hash.new { |h, k| h[k] = [] } + stack = stacks[net_http_args] + + if stack.empty? then + conn = @available.pop connection_args: net_http_args + else + conn = stack.last + end + + stack.push conn + + conn + end + +end + +require 'net/http/persistent/timed_stack_multi' + diff --git a/lib/net/http/persistent/ssl_reuse.rb b/lib/net/http/persistent/ssl_reuse.rb deleted file mode 100644 index 05139703cfb30e32a324b358b98319ab2678cf13..0000000000000000000000000000000000000000 --- a/lib/net/http/persistent/ssl_reuse.rb +++ /dev/null @@ -1,129 +0,0 @@ -## -# This Net::HTTP subclass adds SSL session reuse and Server Name Indication -# (SNI) RFC 3546. -# -# DO NOT DEPEND UPON THIS CLASS -# -# This class is an implementation detail and is subject to change or removal -# at any time. - -class Net::HTTP::Persistent::SSLReuse < Net::HTTP - - @is_proxy_class = false - @proxy_addr = nil - @proxy_port = nil - @proxy_user = nil - @proxy_pass = nil - - def initialize address, port = nil # :nodoc: - super - - @ssl_session = nil - end - - ## - # From ruby trunk r33086 including http://redmine.ruby-lang.org/issues/5341 - - def connect # :nodoc: - D "opening connection to #{conn_address()}..." - s = timeout(@open_timeout) { TCPSocket.open(conn_address(), conn_port()) } - D "opened" - if use_ssl? - ssl_parameters = Hash.new - iv_list = instance_variables - SSL_ATTRIBUTES.each do |name| - ivname = "@#{name}".intern - if iv_list.include?(ivname) and - value = instance_variable_get(ivname) - ssl_parameters[name] = value - end - end - unless @ssl_context then - @ssl_context = OpenSSL::SSL::SSLContext.new - @ssl_context.set_params(ssl_parameters) - end - s = OpenSSL::SSL::SSLSocket.new(s, @ssl_context) - s.sync_close = true - end - @socket = Net::BufferedIO.new(s) - @socket.read_timeout = @read_timeout - @socket.continue_timeout = @continue_timeout if - @socket.respond_to? :continue_timeout - @socket.debug_output = @debug_output - if use_ssl? - begin - if proxy? - @socket.writeline sprintf('CONNECT %s:%s HTTP/%s', - @address, @port, HTTPVersion) - @socket.writeline "Host: #{@address}:#{@port}" - if proxy_user - credential = ["#{proxy_user}:#{proxy_pass}"].pack('m') - credential.delete!("\r\n") - @socket.writeline "Proxy-Authorization: Basic #{credential}" - end - @socket.writeline '' - Net::HTTPResponse.read_new(@socket).value - end - s.session = @ssl_session if @ssl_session - # Server Name Indication (SNI) RFC 3546 - s.hostname = @address if s.respond_to? :hostname= - timeout(@open_timeout) { s.connect } - if @ssl_context.verify_mode != OpenSSL::SSL::VERIFY_NONE - s.post_connection_check(@address) - end - @ssl_session = s.session - rescue => exception - D "Conn close because of connect error #{exception}" - @socket.close if @socket and not @socket.closed? - raise exception - end - end - on_connect - end if RUBY_VERSION > '1.9' - - ## - # From ruby_1_8_7 branch r29865 including a modified - # http://redmine.ruby-lang.org/issues/5341 - - def connect # :nodoc: - D "opening connection to #{conn_address()}..." - s = timeout(@open_timeout) { TCPSocket.open(conn_address(), conn_port()) } - D "opened" - if use_ssl? - unless @ssl_context.verify_mode - warn "warning: peer certificate won't be verified in this SSL session" - @ssl_context.verify_mode = OpenSSL::SSL::VERIFY_NONE - end - s = OpenSSL::SSL::SSLSocket.new(s, @ssl_context) - s.sync_close = true - end - @socket = Net::BufferedIO.new(s) - @socket.read_timeout = @read_timeout - @socket.debug_output = @debug_output - if use_ssl? - if proxy? - @socket.writeline sprintf('CONNECT %s:%s HTTP/%s', - @address, @port, HTTPVersion) - @socket.writeline "Host: #{@address}:#{@port}" - if proxy_user - credential = ["#{proxy_user}:#{proxy_pass}"].pack('m') - credential.delete!("\r\n") - @socket.writeline "Proxy-Authorization: Basic #{credential}" - end - @socket.writeline '' - Net::HTTPResponse.read_new(@socket).value - end - s.session = @ssl_session if @ssl_session - s.connect - if @ssl_context.verify_mode != OpenSSL::SSL::VERIFY_NONE - s.post_connection_check(@address) - end - @ssl_session = s.session - end - on_connect - end if RUBY_VERSION < '1.9' - - private :connect - -end - diff --git a/lib/net/http/persistent/timed_stack_multi.rb b/lib/net/http/persistent/timed_stack_multi.rb new file mode 100644 index 0000000000000000000000000000000000000000..991387d2e1959f679ac90e4bb0e4e89c3c503903 --- /dev/null +++ b/lib/net/http/persistent/timed_stack_multi.rb @@ -0,0 +1,69 @@ +class Net::HTTP::Persistent::TimedStackMulti < ConnectionPool::TimedStack # :nodoc: + + def initialize(size = 0, &block) + super + + @enqueued = 0 + @ques = Hash.new { |h, k| h[k] = [] } + @lru = {} + @key = :"connection_args-#{object_id}" + end + + def empty? + (@created - @enqueued) >= @max + end + + def length + @max - @created + @enqueued + end + + private + + def connection_stored? options = {} # :nodoc: + !@ques[options[:connection_args]].empty? + end + + def fetch_connection options = {} # :nodoc: + connection_args = options[:connection_args] + + @enqueued -= 1 + lru_update connection_args + @ques[connection_args].pop + end + + def lru_update connection_args # :nodoc: + @lru.delete connection_args + @lru[connection_args] = true + end + + def shutdown_connections # :nodoc: + @ques.each_key do |key| + super connection_args: key + end + end + + def store_connection obj, options = {} # :nodoc: + @ques[options[:connection_args]].push obj + @enqueued += 1 + end + + def try_create options = {} # :nodoc: + connection_args = options[:connection_args] + + if @created >= @max && @enqueued >= 1 + oldest, = @lru.first + @lru.delete oldest + @ques[oldest].pop + + @created -= 1 + end + + if @created < @max + @created += 1 + lru_update connection_args + return @create_block.call(connection_args) + end + end + +end + diff --git a/metadata.gz.sig b/metadata.gz.sig index 9ee352956a08b79d7ab0ee4bfa61dc3440f6b92c..379b94c1e578e31d9e2166e0478b0014aeb9ab3c 100644 Binary files a/metadata.gz.sig and b/metadata.gz.sig differ diff --git a/metadata.yml b/metadata.yml deleted file mode 100644 index 12fb19d10424a7317e94a70d4d1204d4a4b3de27..0000000000000000000000000000000000000000 --- a/metadata.yml +++ /dev/null @@ -1,137 +0,0 @@ ---- !ruby/object:Gem::Specification -name: net-http-persistent -version: !ruby/object:Gem::Version - version: 2.9.4 -platform: ruby -authors: -- Eric Hodel -autorequire: -bindir: bin -cert_chain: -- | - -----BEGIN CERTIFICATE----- - MIIDeDCCAmCgAwIBAgIBATANBgkqhkiG9w0BAQUFADBBMRAwDgYDVQQDDAdkcmJy - YWluMRgwFgYKCZImiZPyLGQBGRYIc2VnbWVudDcxEzARBgoJkiaJk/IsZAEZFgNu - ZXQwHhcNMTMwMjI4MDUyMjA4WhcNMTQwMjI4MDUyMjA4WjBBMRAwDgYDVQQDDAdk - cmJyYWluMRgwFgYKCZImiZPyLGQBGRYIc2VnbWVudDcxEzARBgoJkiaJk/IsZAEZ - FgNuZXQwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQCbbgLrGLGIDE76 - LV/cvxdEzCuYuS3oG9PrSZnuDweySUfdp/so0cDq+j8bqy6OzZSw07gdjwFMSd6J - U5ddZCVywn5nnAQ+Ui7jMW54CYt5/H6f2US6U0hQOjJR6cpfiymgxGdfyTiVcvTm - Gj/okWrQl0NjYOYBpDi+9PPmaH2RmLJu0dB/NylsDnW5j6yN1BEI8MfJRR+HRKZY - mUtgzBwF1V4KIZQ8EuL6I/nHVu07i6IkrpAgxpXUfdJQJi0oZAqXurAV3yTxkFwd - g62YrrW26mDe+pZBzR6bpLE+PmXCzz7UxUq3AE0gPHbiMXie3EFE0oxnsU3lIduh - sCANiQ8BAgMBAAGjezB5MAkGA1UdEwQCMAAwCwYDVR0PBAQDAgSwMB0GA1UdDgQW - BBS5k4Z75VSpdM0AclG2UvzFA/VW5DAfBgNVHREEGDAWgRRkcmJyYWluQHNlZ21l - bnQ3Lm5ldDAfBgNVHRIEGDAWgRRkcmJyYWluQHNlZ21lbnQ3Lm5ldDANBgkqhkiG - 9w0BAQUFAAOCAQEAOflo4Md5aJF//EetzXIGZ2EI5PzKWX/mMpp7cxFyDcVPtTv0 - js/6zWrWSbd60W9Kn4ch3nYiATFKhisgeYotDDz2/pb/x1ivJn4vEvs9kYKVvbF8 - V7MV/O5HDW8Q0pA1SljI6GzcOgejtUMxZCyyyDdbUpyAMdt9UpqTZkZ5z1sicgQk - 5o2XJ+OhceOIUVqVh1r6DNY5tLVaGJabtBmJAYFVznDcHiSFybGKBa5n25Egql1t - KDyY1VIazVgoC8XvR4h/95/iScPiuglzA+DBG1hip1xScAtw05BrXyUNrc9CEMYU - wgF94UVoHRp6ywo8I7NP3HcwFQDFNEZPNGXsng== - -----END CERTIFICATE----- -date: 2014-02-10 00:00:00.000000000 Z -dependencies: -- !ruby/object:Gem::Dependency - name: minitest - requirement: !ruby/object:Gem::Requirement - requirements: - - - "~>" - - !ruby/object:Gem::Version - version: '5.2' - type: :development - prerelease: false - version_requirements: !ruby/object:Gem::Requirement - requirements: - - - "~>" - - !ruby/object:Gem::Version - version: '5.2' -- !ruby/object:Gem::Dependency - name: rdoc - requirement: !ruby/object:Gem::Requirement - requirements: - - - "~>" - - !ruby/object:Gem::Version - version: '4.0' - type: :development - prerelease: false - version_requirements: !ruby/object:Gem::Requirement - requirements: - - - "~>" - - !ruby/object:Gem::Version - version: '4.0' -- !ruby/object:Gem::Dependency - name: hoe - requirement: !ruby/object:Gem::Requirement - requirements: - - - "~>" - - !ruby/object:Gem::Version - version: '3.7' - type: :development - prerelease: false - version_requirements: !ruby/object:Gem::Requirement - requirements: - - - "~>" - - !ruby/object:Gem::Version - version: '3.7' -description: |- - Manages persistent connections using Net::HTTP plus a speed fix for Ruby 1.8. - It's thread-safe too! - - Using persistent HTTP connections can dramatically increase the speed of HTTP. - Creating a new HTTP connection for every request involves an extra TCP - round-trip and causes TCP congestion avoidance negotiation to start over. - - Net::HTTP supports persistent connections with some API methods but does not - handle reconnection gracefully. Net::HTTP::Persistent supports reconnection - and retry according to RFC 2616. -email: -- drbrain@segment7.net -executables: [] -extensions: [] -extra_rdoc_files: -- History.txt -- Manifest.txt -- README.rdoc -files: -- ".autotest" -- ".gemtest" -- History.txt -- Manifest.txt -- README.rdoc -- Rakefile -- lib/net/http/faster.rb -- lib/net/http/persistent.rb -- lib/net/http/persistent/ssl_reuse.rb -- test/test_net_http_persistent.rb -- test/test_net_http_persistent_ssl_reuse.rb -homepage: http://docs.seattlerb.org/net-http-persistent -licenses: -- MIT -metadata: {} -post_install_message: -rdoc_options: -- "--main" -- README.rdoc -require_paths: -- lib -required_ruby_version: !ruby/object:Gem::Requirement - requirements: - - - ">=" - - !ruby/object:Gem::Version - version: '0' -required_rubygems_version: !ruby/object:Gem::Requirement - requirements: - - - ">=" - - !ruby/object:Gem::Version - version: '0' -requirements: [] -rubyforge_project: net-http-persistent -rubygems_version: 2.2.1 -signing_key: -specification_version: 4 -summary: Manages persistent connections using Net::HTTP plus a speed fix for Ruby - 1.8 -test_files: -- test/test_net_http_persistent.rb -- test/test_net_http_persistent_ssl_reuse.rb diff --git a/net-http-persistent.gemspec b/net-http-persistent.gemspec new file mode 100644 index 0000000000000000000000000000000000000000..e1c08eaf6e55b15573d926ec526fc8ad47bfdd0a --- /dev/null +++ b/net-http-persistent.gemspec @@ -0,0 +1,47 @@ +######################################################### +# This file has been automatically generated by gem2tgz # +######################################################### +# -*- encoding: utf-8 -*- +# stub: net-http-persistent 3.0.0 ruby lib + +Gem::Specification.new do |s| + s.name = "net-http-persistent".freeze + s.version = "3.0.0" + + s.required_rubygems_version = Gem::Requirement.new(">= 0".freeze) if s.respond_to? :required_rubygems_version= + s.require_paths = ["lib".freeze] + s.authors = ["Eric Hodel".freeze] + s.cert_chain = ["-----BEGIN CERTIFICATE-----\nMIIDNjCCAh6gAwIBAgIBBDANBgkqhkiG9w0BAQUFADBBMRAwDgYDVQQDDAdkcmJy\nYWluMRgwFgYKCZImiZPyLGQBGRYIc2VnbWVudDcxEzARBgoJkiaJk/IsZAEZFgNu\nZXQwHhcNMTYxMDA1MDQyNTQ0WhcNMTcxMDA1MDQyNTQ0WjBBMRAwDgYDVQQDDAdk\ncmJyYWluMRgwFgYKCZImiZPyLGQBGRYIc2VnbWVudDcxEzARBgoJkiaJk/IsZAEZ\nFgNuZXQwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQCbbgLrGLGIDE76\nLV/cvxdEzCuYuS3oG9PrSZnuDweySUfdp/so0cDq+j8bqy6OzZSw07gdjwFMSd6J\nU5ddZCVywn5nnAQ+Ui7jMW54CYt5/H6f2US6U0hQOjJR6cpfiymgxGdfyTiVcvTm\nGj/okWrQl0NjYOYBpDi+9PPmaH2RmLJu0dB/NylsDnW5j6yN1BEI8MfJRR+HRKZY\nmUtgzBwF1V4KIZQ8EuL6I/nHVu07i6IkrpAgxpXUfdJQJi0oZAqXurAV3yTxkFwd\ng62YrrW26mDe+pZBzR6bpLE+PmXCzz7UxUq3AE0gPHbiMXie3EFE0oxnsU3lIduh\nsCANiQ8BAgMBAAGjOTA3MAkGA1UdEwQCMAAwCwYDVR0PBAQDAgSwMB0GA1UdDgQW\nBBS5k4Z75VSpdM0AclG2UvzFA/VW5DANBgkqhkiG9w0BAQUFAAOCAQEAFz46xasn\n5Jx0lPqq6EGpijLIWv+jk+m2v3Ps38M2ZmNpiThmYFBHIqfDCS0UJWDPTj6FJX0A\nrspSuifsHq3CQ3RJImdO9Gewvx6p3WL/xZD1LmuRo6ktWH9gZWiZpA38GfFGj3SZ\n2u6n3qOEsaxIfwYcU4lCgeZ61JdVU+WWK+GfZpCz4BnjA5hgwdFaf5Zb560RtW7S\n77pi/SZtblyK/jqz1hgoMcaYZvIJTqZnen0pHaq+lKY1KzGdTuVbwD3DO+Fi1Vu8\nBOJAX2VNKk4wthxdCu0SvPe7e+QMP2rmaZOyuX4ztiDQiGuoJxyeqoG1WiOttINU\nU76tHMFuL0FUYw==\n-----END CERTIFICATE-----\n".freeze] + s.date = "2016-10-06" + s.description = "Manages persistent connections using Net::HTTP plus a speed fix for Ruby 1.8.\nIt's thread-safe too!\n\nUsing persistent HTTP connections can dramatically increase the speed of HTTP.\nCreating a new HTTP connection for every request involves an extra TCP\nround-trip and causes TCP congestion avoidance negotiation to start over.\n\nNet::HTTP supports persistent connections with some API methods but does not\nhandle reconnection gracefully. Net::HTTP::Persistent supports reconnection\nand retry according to RFC 2616.".freeze + s.email = ["drbrain@segment7.net".freeze] + s.extra_rdoc_files = ["History.txt".freeze, "Manifest.txt".freeze, "README.rdoc".freeze] + s.files = [".autotest".freeze, ".gemtest".freeze, ".travis.yml".freeze, "History.txt".freeze, "Manifest.txt".freeze, "README.rdoc".freeze, "Rakefile".freeze, "lib/net/http/persistent.rb".freeze, "lib/net/http/persistent/connection.rb".freeze, "lib/net/http/persistent/pool.rb".freeze, "lib/net/http/persistent/timed_stack_multi.rb".freeze, "test/test_net_http_persistent.rb".freeze, "test/test_net_http_persistent_timed_stack_multi.rb".freeze] + s.homepage = "http://docs.seattlerb.org/net-http-persistent".freeze + s.licenses = ["MIT".freeze] + s.rdoc_options = ["--main".freeze, "README.rdoc".freeze] + s.required_ruby_version = Gem::Requirement.new("~> 2.1".freeze) + s.rubygems_version = "2.5.2.1".freeze + s.summary = "Manages persistent connections using Net::HTTP plus a speed fix for Ruby 1.8".freeze + + if s.respond_to? :specification_version then + s.specification_version = 4 + + if Gem::Version.new(Gem::VERSION) >= Gem::Version.new('1.2.0') then + s.add_runtime_dependency(%q<connection_pool>.freeze, ["~> 2.2"]) + s.add_development_dependency(%q<hoe>.freeze, ["~> 3.15"]) + s.add_development_dependency(%q<minitest>.freeze, ["~> 5.8"]) + s.add_development_dependency(%q<rdoc>.freeze, ["~> 4.0"]) + else + s.add_dependency(%q<connection_pool>.freeze, ["~> 2.2"]) + s.add_dependency(%q<hoe>.freeze, ["~> 3.15"]) + s.add_dependency(%q<minitest>.freeze, ["~> 5.8"]) + s.add_dependency(%q<rdoc>.freeze, ["~> 4.0"]) + end + else + s.add_dependency(%q<connection_pool>.freeze, ["~> 2.2"]) + s.add_dependency(%q<hoe>.freeze, ["~> 3.15"]) + s.add_dependency(%q<minitest>.freeze, ["~> 5.8"]) + s.add_dependency(%q<rdoc>.freeze, ["~> 4.0"]) + end +end diff --git a/test/test_net_http_persistent.rb b/test/test_net_http_persistent.rb index 2cc5f89994499b79af698d014ce441389f8a8846..fa64570ac3ffb9babacc7dd03b7198886d2e5fd5 100644 --- a/test/test_net_http_persistent.rb +++ b/test/test_net_http_persistent.rb @@ -51,21 +51,9 @@ class Net::HTTP include Net::HTTP::Persistent::TestConnect end -class Net::HTTP::Persistent::SSLReuse - include Net::HTTP::Persistent::TestConnect -end - class TestNetHttpPersistent < Minitest::Test - RUBY_1 = RUBY_VERSION < '2' - def setup - @http_class = if RUBY_1 and HAVE_OPENSSL then - Net::HTTP::Persistent::SSLReuse - else - Net::HTTP - end - @http = Net::HTTP::Persistent.new @uri = URI.parse 'http://example.com/path' @@ -80,25 +68,23 @@ class TestNetHttpPersistent < Minitest::Test ENV.delete 'NO_PROXY' Net::HTTP.use_connect :test_connect - Net::HTTP::Persistent::SSLReuse.use_connect :test_connect end def teardown - Thread.current.keys.each do |key| - Thread.current[key] = nil - end - Net::HTTP.use_connect :orig_connect - Net::HTTP::Persistent::SSLReuse.use_connect :orig_connect end class BasicConnection - attr_accessor :started, :finished, :address, :port, - :read_timeout, :open_timeout + attr_accessor :started, :finished, :address, :port, :use_ssl, + :read_timeout, :open_timeout, :keep_alive_timeout + attr_accessor :ciphers, :ssl_timeout, :ssl_version, + :verify_depth, :verify_mode, :cert_store, + :ca_file, :ca_path, :cert, :key attr_reader :req, :debug_output def initialize @started, @finished = 0, 0 @address, @port = 'example.com', 80 + @use_ssl = false end def finish @finished += 1 @@ -125,21 +111,32 @@ class TestNetHttpPersistent < Minitest::Test def started? @started >= 1 end + def proxy_address + end + def proxy_port + end end def basic_connection raise "#{@uri} is not HTTP" unless @uri.scheme.downcase == 'http' - c = BasicConnection.new - conns[0]["#{@uri.host}:#{@uri.port}"] = c - c + net_http_args = [@uri.host, @uri.port] + + connection = Net::HTTP::Persistent::Connection.allocate + connection.ssl_generation = @http.ssl_generation + connection.http = BasicConnection.new + connection.reset + + @http.pool.available.push connection, connection_args: net_http_args + + connection end def connection - c = basic_connection - touts[c.object_id] = Time.now + connection = basic_connection + connection.last_use = Time.now - def c.request(req) + def (connection.http).request(req) @req = req r = Net::HTTPResponse.allocate r.instance_variable_set :@header, {} @@ -149,30 +146,22 @@ class TestNetHttpPersistent < Minitest::Test r end - c + connection end - def conns - Thread.current[@http.generation_key] ||= Hash.new { |h,k| h[k] = {} } - end + def ssl_connection + raise "#{@uri} is not HTTPS" unless @uri.scheme.downcase == 'https' - def reqs - Thread.current[@http.request_key] ||= Hash.new 0 - end + net_http_args = [@uri.host, @uri.port] - def ssl_conns - Thread.current[@http.ssl_generation_key] ||= Hash.new { |h,k| h[k] = {} } - end + connection = Net::HTTP::Persistent::Connection.allocate + connection.ssl_generation = @http.ssl_generation + connection.http = BasicConnection.new + connection.reset - def ssl_connection generation = 0 - raise "#{@uri} is not HTTPS" unless @uri.scheme.downcase == 'https' - c = BasicConnection.new - ssl_conns[generation]["#{@uri.host}:#{@uri.port}"] = c - c - end + @http.pool.available.push connection, connection_args: net_http_args - def touts - Thread.current[@http.timeout_key] ||= Hash.new Net::HTTP::Persistent::EPOCH + connection end def test_initialize @@ -188,7 +177,7 @@ class TestNetHttpPersistent < Minitest::Test end def test_initialize_name - http = Net::HTTP::Persistent.new 'name' + http = Net::HTTP::Persistent.new name: 'name' assert_equal 'name', http.name end @@ -212,7 +201,7 @@ class TestNetHttpPersistent < Minitest::Test def test_initialize_proxy proxy_uri = URI.parse 'http://proxy.example' - http = Net::HTTP::Persistent.new nil, proxy_uri + http = Net::HTTP::Persistent.new proxy: proxy_uri assert_equal proxy_uri, http.proxy_uri end @@ -224,6 +213,13 @@ class TestNetHttpPersistent < Minitest::Test assert_equal 1, @http.ssl_generation end + def test_ca_path_equals + @http.ca_path = :ca_path + + assert_equal :ca_path, @http.ca_path + assert_equal 1, @http.ssl_generation + end + def test_can_retry_eh_change_requests post = Net::HTTP::Post.new '/' @@ -234,27 +230,14 @@ class TestNetHttpPersistent < Minitest::Test assert @http.can_retry? post end - if RUBY_1 then - def test_can_retry_eh_idempotent - head = Net::HTTP::Head.new '/' + def test_can_retry_eh_idempotent + head = Net::HTTP::Head.new '/' - assert @http.can_retry? head + refute @http.can_retry? head - post = Net::HTTP::Post.new '/' - - refute @http.can_retry? post - end - else - def test_can_retry_eh_idempotent - head = Net::HTTP::Head.new '/' - - assert @http.can_retry? head - refute @http.can_retry? head, true - - post = Net::HTTP::Post.new '/' + post = Net::HTTP::Post.new '/' - refute @http.can_retry? post - end + refute @http.can_retry? post end def test_cert_store_equals @@ -271,168 +254,154 @@ class TestNetHttpPersistent < Minitest::Test assert_equal 1, @http.ssl_generation end + def test_ciphers_equals + @http.ciphers = :ciphers + + assert_equal :ciphers, @http.ciphers + assert_equal 1, @http.ssl_generation + end + def test_connection_for @http.open_timeout = 123 @http.read_timeout = 321 @http.idle_timeout = 42 - c = @http.connection_for @uri - assert_kind_of @http_class, c + used = @http.connection_for @uri do |c| + assert_kind_of Net::HTTP, c.http - assert c.started? - refute c.proxy? + assert c.http.started? + refute c.http.proxy? - assert_equal 123, c.open_timeout - assert_equal 321, c.read_timeout - assert_equal 42, c.keep_alive_timeout unless RUBY_1 + assert_equal 123, c.http.open_timeout + assert_equal 321, c.http.read_timeout + assert_equal 42, c.http.keep_alive_timeout - assert_includes conns[0].keys, 'example.com:80' - assert_same c, conns[0]['example.com:80'] + c + end + + stored = @http.pool.checkout ['example.com', 80] + + assert_same used, stored end def test_connection_for_cached cached = basic_connection - cached.start - conns[0]['example.com:80'] = cached + cached.http.start @http.read_timeout = 5 - c = @http.connection_for @uri - - assert c.started? + @http.connection_for @uri do |c| + assert c.http.started? - assert_equal 5, c.read_timeout + assert_equal 5, c.http.read_timeout - assert_same cached, c + assert_same cached, c + end end def test_connection_for_closed cached = basic_connection - cached.start + cached.http.start if Socket.const_defined? :TCP_NODELAY then io = Object.new def io.setsockopt(*a) raise IOError, 'closed stream' end cached.instance_variable_set :@socket, Net::BufferedIO.new(io) end - conns['example.com:80'] = cached - - c = @http.connection_for @uri - - assert c.started? - assert_includes conns.keys, 'example.com:80' - assert_same c, conns[0]['example.com:80'] + @http.connection_for @uri do |c| + assert c.http.started? - socket = c.instance_variable_get :@socket + socket = c.http.instance_variable_get :@socket - refute_includes socket.io.instance_variables, :@setsockopt - refute_includes socket.io.instance_variables, '@setsockopt' + refute_includes socket.io.instance_variables, :@setsockopt + refute_includes socket.io.instance_variables, '@setsockopt' + end end def test_connection_for_debug_output io = StringIO.new @http.debug_output = io - c = @http.connection_for @uri - - assert c.started? - assert_equal io, c.instance_variable_get(:@debug_output) - - assert_includes conns[0].keys, 'example.com:80' - assert_same c, conns[0]['example.com:80'] + @http.connection_for @uri do |c| + assert c.http.started? + assert_equal io, c.http.instance_variable_get(:@debug_output) + end end def test_connection_for_cached_expire_always cached = basic_connection - cached.start - conns[0]['example.com:80'] = cached - reqs[cached.object_id] = 10 - touts[cached.object_id] = Time.now # last used right now + cached.http.start + cached.requests = 10 + cached.last_use = Time.now # last used right now @http.idle_timeout = 0 - c = @http.connection_for @uri + @http.connection_for @uri do |c| + assert c.http.started? - assert c.started? + assert_same cached, c - assert_same cached, c - - assert_equal 0, reqs[cached.object_id], - 'connection reset due to timeout' + assert_equal 0, c.requests, 'connection reset due to timeout' + end end def test_connection_for_cached_expire_never cached = basic_connection - cached.start - conns[0]['example.com:80'] = cached - reqs[cached.object_id] = 10 - touts[cached.object_id] = Time.now # last used right now + cached.http.start + cached.requests = 10 + cached.last_use = Time.now # last used right now @http.idle_timeout = nil - c = @http.connection_for @uri - - assert c.started? + @http.connection_for @uri do |c| + assert c.http.started? - assert_same cached, c + assert_same cached, c - assert_equal 10, reqs[cached.object_id], - 'connection reset despite no timeout' + assert_equal 10, c.requests, 'connection reset despite no timeout' + end end def test_connection_for_cached_expired cached = basic_connection - cached.start - conns[0]['example.com:80'] = cached - reqs[cached.object_id] = 10 - touts[cached.object_id] = Time.now - 3600 + cached.http.start + cached.requests = 10 + cached.last_use = Time.now - 3600 - c = @http.connection_for @uri + @http.connection_for @uri do |c| + assert c.http.started? - assert c.started? - - assert_same cached, c - assert_equal 0, reqs[cached.object_id], - 'connection not reset due to timeout' - end - - def test_connection_for_refused - Net::HTTP.use_connect :refused_connect - Net::HTTP::Persistent::SSLReuse.use_connect :refused_connect - - e = assert_raises Net::HTTP::Persistent::Error do - @http.connection_for @uri + assert_same cached, c + assert_equal 0, cached.requests, 'connection not reset due to timeout' end - - assert_equal 'connection refused: example.com:80', e.message end def test_connection_for_finished_ssl skip 'OpenSSL is missing' unless HAVE_OPENSSL uri = URI.parse 'https://example.com/path' - c = @http.connection_for uri - assert c.started? - assert c.use_ssl? + @http.connection_for uri do |c| + assert c.http.started? + assert c.http.use_ssl? - @http.finish c + @http.finish c - refute c.started? - - c2 = @http.connection_for uri + refute c.http.started? + end - assert c2.started? + @http.connection_for uri do |c2| + assert c2.http.started? + end end def test_connection_for_host_down - cached = basic_connection - def cached.start; raise Errno::EHOSTDOWN end - def cached.started?; false end - conns[0]['example.com:80'] = cached + c = basic_connection + def (c.http).start; raise Errno::EHOSTDOWN end + def (c.http).started?; false end e = assert_raises Net::HTTP::Persistent::Error do - @http.connection_for @uri + @http.connection_for @uri do end end assert_equal 'host down: example.com:80', e.message @@ -440,8 +409,10 @@ class TestNetHttpPersistent < Minitest::Test def test_connection_for_http_class_with_fakeweb Object.send :const_set, :FakeWeb, nil - c = @http.connection_for @uri - assert_instance_of Net::HTTP, c + + @http.connection_for @uri do |c| + assert_instance_of Net::HTTP, c.http + end ensure if Object.const_defined?(:FakeWeb) then Object.send :remove_const, :FakeWeb @@ -450,8 +421,9 @@ class TestNetHttpPersistent < Minitest::Test def test_connection_for_http_class_with_webmock Object.send :const_set, :WebMock, nil - c = @http.connection_for @uri - assert_instance_of Net::HTTP, c + @http.connection_for @uri do |c| + assert_instance_of Net::HTTP, c.http + end ensure if Object.const_defined?(:WebMock) then Object.send :remove_const, :WebMock @@ -460,8 +432,9 @@ class TestNetHttpPersistent < Minitest::Test def test_connection_for_http_class_with_artifice Object.send :const_set, :Artifice, nil - c = @http.connection_for @uri - assert_instance_of Net::HTTP, c + @http.connection_for @uri do |c| + assert_instance_of Net::HTTP, c.http + end ensure if Object.const_defined?(:Artifice) then Object.send :remove_const, :Artifice @@ -469,23 +442,12 @@ class TestNetHttpPersistent < Minitest::Test end def test_connection_for_name - http = Net::HTTP::Persistent.new 'name' + http = Net::HTTP::Persistent.new name: 'name' uri = URI.parse 'http://example/' - c = http.connection_for uri - - assert c.started? - - refute_includes conns.keys, 'example:80' - end - - def test_connection_for_no_ssl_reuse - @http.reuse_ssl_sessions = false - @http.open_timeout = 123 - @http.read_timeout = 321 - c = @http.connection_for @uri - - assert_instance_of Net::HTTP, c + http.connection_for uri do |c| + assert c.http.started? + end end def test_connection_for_proxy @@ -493,16 +455,20 @@ class TestNetHttpPersistent < Minitest::Test uri.user = 'johndoe' uri.password = 'muffins' - http = Net::HTTP::Persistent.new nil, uri + http = Net::HTTP::Persistent.new proxy: uri - c = http.connection_for @uri + used = http.connection_for @uri do |c| + assert c.http.started? + assert c.http.proxy? - assert c.started? - assert c.proxy? + c + end + + stored = http.pool.checkout ['example.com', 80, + 'proxy.example', 80, + 'johndoe', 'muffins'] - assert_includes conns[1].keys, - 'example.com:80:proxy.example:80:johndoe:muffins' - assert_same c, conns[1]['example.com:80:proxy.example:80:johndoe:muffins'] + assert_same used, stored end def test_connection_for_proxy_unescaped @@ -511,25 +477,28 @@ class TestNetHttpPersistent < Minitest::Test uri.password = 'muf%3Afins' uri.freeze - http = Net::HTTP::Persistent.new nil, uri - c = http.connection_for @uri + http = Net::HTTP::Persistent.new proxy: uri + + http.connection_for @uri do end + + stored = http.pool.checkout ['example.com', 80, + 'proxy.example', 80, + 'john@doe', 'muf:fins'] - assert_includes conns[1].keys, - 'example.com:80:proxy.example:80:john@doe:muf:fins' + assert stored end def test_connection_for_proxy_host_down Net::HTTP.use_connect :host_down_connect - Net::HTTP::Persistent::SSLReuse.use_connect :host_down_connect uri = URI.parse 'http://proxy.example' uri.user = 'johndoe' uri.password = 'muffins' - http = Net::HTTP::Persistent.new nil, uri + http = Net::HTTP::Persistent.new proxy: uri e = assert_raises Net::HTTP::Persistent::Error do - http.connection_for @uri + http.connection_for @uri do end end assert_equal 'host down: proxy.example:80', e.message @@ -537,16 +506,15 @@ class TestNetHttpPersistent < Minitest::Test def test_connection_for_proxy_refused Net::HTTP.use_connect :refused_connect - Net::HTTP::Persistent::SSLReuse.use_connect :refused_connect uri = URI.parse 'http://proxy.example' uri.user = 'johndoe' uri.password = 'muffins' - http = Net::HTTP::Persistent.new nil, uri + http = Net::HTTP::Persistent.new proxy: uri e = assert_raises Net::HTTP::Persistent::Error do - http.connection_for @uri + http.connection_for @uri do end end assert_equal 'connection refused: proxy.example:80', e.message @@ -558,38 +526,37 @@ class TestNetHttpPersistent < Minitest::Test uri.password = 'muffins' uri.query = 'no_proxy=example.com' - http = Net::HTTP::Persistent.new nil, uri + http = Net::HTTP::Persistent.new proxy: uri - c = http.connection_for @uri + http.connection_for @uri do |c| + assert c.http.started? + refute c.http.proxy? + end - assert c.started? - refute c.proxy? + stored = http.pool.checkout ['example.com', 80] - assert_includes conns[1].keys, 'example.com:80' - assert_same c, conns[1]['example.com:80'] + assert stored end def test_connection_for_refused - cached = basic_connection - def cached.start; raise Errno::ECONNREFUSED end - def cached.started?; false end - conns[0]['example.com:80'] = cached + Net::HTTP.use_connect :refused_connect e = assert_raises Net::HTTP::Persistent::Error do - @http.connection_for @uri + @http.connection_for @uri do end end - assert_match %r%connection refused%, e.message + assert_equal 'connection refused: example.com:80', e.message end def test_connection_for_ssl skip 'OpenSSL is missing' unless HAVE_OPENSSL uri = URI.parse 'https://example.com/path' - c = @http.connection_for uri - assert c.started? - assert c.use_ssl? + @http.connection_for uri do |c| + assert c.http.started? + assert c.http.use_ssl? + end end def test_connection_for_ssl_cached @@ -597,11 +564,11 @@ class TestNetHttpPersistent < Minitest::Test @uri = URI.parse 'https://example.com/path' - cached = ssl_connection 0 - - c = @http.connection_for @uri + cached = ssl_connection - assert_same cached, c + @http.connection_for @uri do |c| + assert_same cached, c + end end def test_connection_for_ssl_cached_reconnect @@ -611,45 +578,47 @@ class TestNetHttpPersistent < Minitest::Test cached = ssl_connection - @http.reconnect_ssl + ssl_generation = @http.ssl_generation - c = @http.connection_for @uri + @http.reconnect_ssl - refute_same cached, c + @http.connection_for @uri do |c| + assert_same cached, c + refute_equal ssl_generation, c.ssl_generation + end end def test_connection_for_ssl_case skip 'OpenSSL is missing' unless HAVE_OPENSSL uri = URI.parse 'HTTPS://example.com/path' - c = @http.connection_for uri - - assert c.started? - assert c.use_ssl? + @http.connection_for uri do |c| + assert c.http.started? + assert c.http.use_ssl? + end end def test_connection_for_timeout cached = basic_connection - cached.start - reqs[cached.object_id] = 10 - touts[cached.object_id] = Time.now - 6 - conns[0]['example.com:80'] = cached + cached.http.start + cached.requests = 10 + cached.last_use = Time.now - 6 - c = @http.connection_for @uri + @http.connection_for @uri do |c| + assert c.http.started? + assert_equal 0, c.requests - assert c.started? - assert_equal 0, reqs[c.object_id] - - assert_same cached, c + assert_same cached, c + end end def test_error_message c = basic_connection - touts[c.object_id] = Time.now - 1 - reqs[c.object_id] = 5 + c.last_use = Time.now - 1 + c.requests = 5 - message = @http.error_message(c) - assert_match %r%after 4 requests on #{c.object_id}%, message + message = @http.error_message c + assert_match %r%after 4 requests on #{c.http.object_id}%, message assert_match %r%, last used [\d.]+ seconds ago%, message end @@ -667,8 +636,8 @@ class TestNetHttpPersistent < Minitest::Test def test_expired_eh c = basic_connection - reqs[c.object_id] = 0 - touts[c.object_id] = Time.now - 11 + c.requests = 0 + c.last_use = Time.now - 11 @http.idle_timeout = 0 assert @http.expired? c @@ -688,41 +657,57 @@ class TestNetHttpPersistent < Minitest::Test def test_expired_due_to_max_requests c = basic_connection - reqs[c.object_id] = 0 - touts[c.object_id] = Time.now + c.requests = 0 + c.last_use = Time.now refute @http.expired? c - reqs[c.object_id] = 10 + c.requests = 10 refute @http.expired? c @http.max_requests = 10 assert @http.expired? c - reqs[c.object_id] = 9 + c.requests = 9 refute @http.expired? c end def test_finish c = basic_connection - reqs[c.object_id] = 5 + c.requests = 5 @http.finish c - refute c.started? - assert c.finished? - assert_equal 0, reqs[c.object_id] + refute c.http.started? + assert c.http.finished? + + assert_equal 0, c.requests + assert_equal Net::HTTP::Persistent::EPOCH, c.last_use end def test_finish_io_error c = basic_connection - def c.finish; @finished += 1; raise IOError end - reqs[c.object_id] = 5 + def (c.http).finish; @finished += 1; raise IOError end + c.requests = 5 @http.finish c - refute c.started? - assert c.finished? + refute c.http.started? + assert c.http.finished? + end + + def test_finish_ssl_no_session_reuse + http = Net::HTTP.new 'localhost', 443, ssl: true + http.instance_variable_set :@ssl_session, :something + + c = Net::HTTP::Persistent::Connection.allocate + c.instance_variable_set :@http, http + + @http.reuse_ssl_sessions = false + + @http.finish c + + assert_nil c.http.instance_variable_get :@ssl_session end def test_http_version @@ -746,14 +731,6 @@ class TestNetHttpPersistent < Minitest::Test refute @http.idempotent? Net::HTTP::Post.new '/' end - def test_max_age - assert_in_delta Time.now - 5, @http.max_age - - @http.idle_timeout = nil - - assert_in_delta Time.now + 1, @http.max_age - end - def test_normalize_uri assert_equal 'http://example', @http.normalize_uri('example') assert_equal 'http://example', @http.normalize_uri('http://example') @@ -775,7 +752,6 @@ class TestNetHttpPersistent < Minitest::Test cached = basic_connection cached.start - conns['example.com:80'] = cached requests = [ Net::HTTP::Get.new((@uri + '1').request_uri), @@ -935,9 +911,38 @@ class TestNetHttpPersistent < Minitest::Test end def test_reconnect_ssl - result = @http.reconnect_ssl + skip 'OpenSSL is missing' unless HAVE_OPENSSL - assert_equal 1, result + @uri = URI 'https://example.com' + now = Time.now + + ssl_http = ssl_connection + + def (ssl_http.http).finish + @started = 0 + end + + used1 = @http.connection_for @uri do |c| + c.requests = 1 + c.last_use = now + c + end + + assert_equal OpenSSL::SSL::VERIFY_PEER, used1.http.verify_mode + + @http.verify_mode = OpenSSL::SSL::VERIFY_NONE + @http.reconnect_ssl + + used2 = @http.connection_for @uri do |c| + c + end + + assert_same used1, used2 + + assert_equal OpenSSL::SSL::VERIFY_NONE, used2.http.verify_mode, + 'verify mode must change' + assert_equal 0, used2.requests + assert_equal Net::HTTP::Persistent::EPOCH, used2.last_use end def test_request @@ -946,7 +951,7 @@ class TestNetHttpPersistent < Minitest::Test c = connection res = @http.request @uri - req = c.req + req = c.http.req assert_kind_of Net::HTTPResponse, res @@ -959,72 +964,51 @@ class TestNetHttpPersistent < Minitest::Test assert_equal 'keep-alive', req['connection'] assert_equal '30', req['keep-alive'] - assert_in_delta Time.now, touts[c.object_id] + assert_in_delta Time.now, c.last_use - assert_equal 1, reqs[c.object_id] + assert_equal 1, c.requests end def test_request_ETIMEDOUT c = basic_connection - def c.request(*a) raise Errno::ETIMEDOUT, "timed out" end + def (c.http).request(*a) raise Errno::ETIMEDOUT, "timed out" end e = assert_raises Net::HTTP::Persistent::Error do @http.request @uri end - assert_equal 0, reqs[c.object_id] + assert_equal 0, c.requests assert_match %r%too many connection resets%, e.message end def test_request_bad_response c = basic_connection - def c.request(*a) raise Net::HTTPBadResponse end + def (c.http).request(*a) raise Net::HTTPBadResponse end e = assert_raises Net::HTTP::Persistent::Error do @http.request @uri end - assert_equal 0, reqs[c.object_id] + assert_equal 0, c.requests assert_match %r%too many bad responses%, e.message end - if RUBY_1 then - def test_request_bad_response_retry - c = basic_connection - def c.request(*a) - if defined? @called then - r = Net::HTTPResponse.allocate - r.instance_variable_set :@header, {} - def r.http_version() '1.1' end - r - else - @called = true - raise Net::HTTPBadResponse - end - end + def test_request_bad_response_retry + c = basic_connection + def (c.http).request(*a) + raise Net::HTTPBadResponse + end + assert_raises Net::HTTP::Persistent::Error do @http.request @uri - - assert c.finished? end - else - def test_request_bad_response_retry - c = basic_connection - def c.request(*a) - raise Net::HTTPBadResponse - end - - assert_raises Net::HTTP::Persistent::Error do - @http.request @uri - end - assert c.finished? - end + assert c.http.finished? end def test_request_bad_response_unsafe c = basic_connection - def c.request(*a) + def (c.http).request(*a) if instance_variable_defined? :@request then raise 'POST must not be retried' else @@ -1037,7 +1021,7 @@ class TestNetHttpPersistent < Minitest::Test @http.request @uri, Net::HTTP::Post.new(@uri.path) end - assert_equal 0, reqs[c.object_id] + assert_equal 0, c.requests assert_match %r%too many bad responses%, e.message end @@ -1050,7 +1034,7 @@ class TestNetHttpPersistent < Minitest::Test body = r.read_body end - req = c.req + req = c.http.req assert_kind_of Net::HTTPResponse, res refute_nil body @@ -1061,17 +1045,17 @@ class TestNetHttpPersistent < Minitest::Test assert_equal '30', req['keep-alive'] assert_match %r%test ua%, req['user-agent'] - assert_equal 1, reqs[c.object_id] + assert_equal 1, c.requests end def test_request_close_1_0 c = connection - class << c + class << c.http remove_method :request end - def c.request req + def (c.http).request req @req = req r = Net::HTTPResponse.allocate r.instance_variable_set :@header, {} @@ -1084,7 +1068,7 @@ class TestNetHttpPersistent < Minitest::Test request = Net::HTTP::Get.new @uri.request_uri res = @http.request @uri, request - req = c.req + req = c.http.req assert_kind_of Net::HTTPResponse, res @@ -1093,7 +1077,7 @@ class TestNetHttpPersistent < Minitest::Test assert_equal 'keep-alive', req['connection'] assert_equal '30', req['keep-alive'] - assert c.finished? + assert c.http.finished? end def test_request_connection_close_request @@ -1103,7 +1087,7 @@ class TestNetHttpPersistent < Minitest::Test request['connection'] = 'close' res = @http.request @uri, request - req = c.req + req = c.http.req assert_kind_of Net::HTTPResponse, res @@ -1112,17 +1096,17 @@ class TestNetHttpPersistent < Minitest::Test assert_equal 'close', req['connection'] assert_equal nil, req['keep-alive'] - assert c.finished? + assert c.http.finished? end def test_request_connection_close_response c = connection - class << c + class << c.http remove_method :request end - def c.request req + def (c.http).request req @req = req r = Net::HTTPResponse.allocate r.instance_variable_set :@header, {} @@ -1136,7 +1120,7 @@ class TestNetHttpPersistent < Minitest::Test request = Net::HTTP::Get.new @uri.request_uri res = @http.request @uri, request - req = c.req + req = c.http.req assert_kind_of Net::HTTPResponse, res @@ -1145,120 +1129,77 @@ class TestNetHttpPersistent < Minitest::Test assert_equal 'keep-alive', req['connection'] assert_equal '30', req['keep-alive'] - assert c.finished? + assert c.http.finished? end def test_request_exception c = basic_connection - def c.request(*a) raise Exception, "very bad things happened" end + def (c.http).request(*a) + raise Exception, "very bad things happened" + end assert_raises Exception do @http.request @uri end - assert_equal 0, reqs[c.object_id] - assert c.finished? + assert_equal 0, c.requests + assert c.http.finished? end def test_request_invalid c = basic_connection - def c.request(*a) raise Errno::EINVAL, "write" end + def (c.http).request(*a) raise Errno::EINVAL, "write" end e = assert_raises Net::HTTP::Persistent::Error do @http.request @uri end - assert_equal 0, reqs[c.object_id] + assert_equal 0, c.requests assert_match %r%too many connection resets%, e.message end - def test_request_invalid_retry - c = basic_connection - touts[c.object_id] = Time.now - - def c.request(*a) - if defined? @called then - r = Net::HTTPResponse.allocate - r.instance_variable_set :@header, {} - def r.http_version() '1.1' end - r - else - @called = true - raise Errno::EINVAL, "write" - end - end - - @http.request @uri - - assert c.reset? - assert c.finished? - end - def test_request_post c = connection post = Net::HTTP::Post.new @uri.path @http.request @uri, post - req = c.req + req = c.http.req assert_same post, req end def test_request_reset c = basic_connection - def c.request(*a) raise Errno::ECONNRESET end + def (c.http).request(*a) raise Errno::ECONNRESET end e = assert_raises Net::HTTP::Persistent::Error do @http.request @uri end - assert_equal 0, reqs[c.object_id] + assert_equal 0, c.requests assert_match %r%too many connection resets%, e.message end - if RUBY_1 then - def test_request_reset_retry - c = basic_connection - touts[c.object_id] = Time.now - def c.request(*a) - if defined? @called then - r = Net::HTTPResponse.allocate - r.instance_variable_set :@header, {} - def r.http_version() '1.1' end - r - else - @called = true - raise Errno::ECONNRESET - end - end - - @http.request @uri + def test_request_reset_retry + c = basic_connection + c.last_use = Time.now - assert c.reset? - assert c.finished? + def (c.http).request(*a) + raise Errno::ECONNRESET end - else - def test_request_reset_retry - c = basic_connection - touts[c.object_id] = Time.now - def c.request(*a) - raise Errno::ECONNRESET - end - - assert_raises Net::HTTP::Persistent::Error do - @http.request @uri - end - - refute c.reset? - assert c.finished? + assert_raises Net::HTTP::Persistent::Error do + @http.request @uri end + + refute (c.http).reset? + assert (c.http).finished? end def test_request_reset_unsafe c = basic_connection - def c.request(*a) + def (c.http).request(*a) if instance_variable_defined? :@request then raise 'POST must not be retried' else @@ -1271,7 +1212,7 @@ class TestNetHttpPersistent < Minitest::Test @http.request @uri, Net::HTTP::Post.new(@uri.path) end - assert_equal 0, reqs[c.object_id] + assert_equal 0, c.requests assert_match %r%too many connection resets%, e.message end @@ -1279,15 +1220,16 @@ class TestNetHttpPersistent < Minitest::Test skip 'OpenSSL is missing' unless HAVE_OPENSSL uri = URI.parse 'https://example.com/path' - c = @http.connection_for uri - def c.request(*) - raise OpenSSL::SSL::SSLError, "SSL3_WRITE_PENDING:bad write retry" - end + @http.connection_for uri do |c| + def (c.http).request(*) + raise OpenSSL::SSL::SSLError, "SSL3_WRITE_PENDING:bad write retry" + end - e = assert_raises Net::HTTP::Persistent::Error do - @http.request uri + e = assert_raises Net::HTTP::Persistent::Error do + @http.request uri + end + assert_match %r%bad write retry%, e.message end - assert_match %r%bad write retry%, e.message end def test_request_setup @@ -1308,6 +1250,22 @@ class TestNetHttpPersistent < Minitest::Test assert_equal '30', req['keep-alive'] end + def test_request_string + @http.override_headers['user-agent'] = 'test ua' + @http.headers['accept'] = 'text/*' + c = connection + + res = @http.request @uri.to_s + req = c.http.req + + assert_kind_of Net::HTTPResponse, res + + assert_kind_of Net::HTTP::Get, req + assert_equal '/path', req.path + + assert_equal 1, c.requests + end + def test_request_setup_uri uri = @uri + '?a=b' @@ -1319,8 +1277,8 @@ class TestNetHttpPersistent < Minitest::Test def test_request_failed c = basic_connection - reqs[c.object_id] = 1 - touts[c.object_id] = Time.now + c.requests = 1 + c.last_use = Time.now original = nil @@ -1330,7 +1288,7 @@ class TestNetHttpPersistent < Minitest::Test end req = Net::HTTP::Get.new '/' - + e = assert_raises Net::HTTP::Persistent::Error do @http.request_failed original, req, c end @@ -1343,24 +1301,24 @@ class TestNetHttpPersistent < Minitest::Test def test_reset c = basic_connection - c.start - touts[c.object_id] = Time.now - reqs[c.object_id] = 5 + c.http.start + c.last_use = Time.now + c.requests = 5 @http.reset c - assert c.started? - assert c.finished? - assert c.reset? - assert_equal 0, reqs[c.object_id] - assert_equal Net::HTTP::Persistent::EPOCH, touts[c.object_id] + assert c.http.started? + assert c.http.finished? + assert c.http.reset? + assert_equal 0, c.requests + assert_equal Net::HTTP::Persistent::EPOCH, c.last_use end def test_reset_host_down c = basic_connection - touts[c.object_id] = Time.now - def c.start; raise Errno::EHOSTDOWN end - reqs[c.object_id] = 5 + c.last_use = Time.now + def (c.http).start; raise Errno::EHOSTDOWN end + c.requests = 5 e = assert_raises Net::HTTP::Persistent::Error do @http.reset c @@ -1372,20 +1330,20 @@ class TestNetHttpPersistent < Minitest::Test def test_reset_io_error c = basic_connection - touts[c.object_id] = Time.now - reqs[c.object_id] = 5 + c.last_use = Time.now + c.requests = 5 @http.reset c - assert c.started? - assert c.finished? + assert c.http.started? + assert c.http.finished? end def test_reset_refused c = basic_connection - touts[c.object_id] = Time.now - def c.start; raise Errno::ECONNREFUSED end - reqs[c.object_id] = 5 + c.last_use = Time.now + def (c.http).start; raise Errno::ECONNREFUSED end + c.requests = 5 e = assert_raises Net::HTTP::Persistent::Error do @http.reset c @@ -1401,161 +1359,50 @@ class TestNetHttpPersistent < Minitest::Test refute @http.retry_change_requests - if RUBY_1 then - assert @http.can_retry?(get) - else - assert @http.can_retry?(get) - end - refute @http.can_retry?(get, true) + refute @http.can_retry?(get) refute @http.can_retry?(post) @http.retry_change_requests = true assert @http.retry_change_requests - if RUBY_1 then - assert @http.can_retry?(get) - else - assert @http.can_retry?(get) - refute @http.can_retry?(get, true) - end - + refute @http.can_retry?(get) assert @http.can_retry?(post) end def test_shutdown - ssl_conns c = connection - rs = reqs - ts = touts orig = @http - @http = Net::HTTP::Persistent.new 'name' + @http = Net::HTTP::Persistent.new name: 'name' c2 = connection orig.shutdown @http = orig - assert c.finished?, 'last-generation connection must be finished' - refute c2.finished?, 'present generation connection must not be finished' - - refute_same rs, reqs - refute_same ts, touts - - assert_empty conns - assert_empty ssl_conns - - assert_empty reqs - assert_empty touts - end - - def test_shutdown_in_all_threads - conns - ssl_conns - - t = Thread.new do - c = connection - ssl_conns - conns - reqs - - Thread.stop - - c - end - - Thread.pass until t.status == 'sleep' - - c = connection - - assert_nil @http.shutdown_in_all_threads - - assert c.finished?, 'connection in same thread must be finished' - - assert_empty Thread.current[@http.generation_key] - - assert_nil Thread.current[@http.request_key] - - t.run - assert t.value.finished?, 'connection in other thread must be finished' - - assert_empty t[@http.generation_key] - - assert_nil t[@http.request_key] - end - - def test_shutdown_no_connections - @http.shutdown - - assert_nil Thread.current[@http.generation_key] - assert_nil Thread.current[@http.ssl_generation_key] - - assert_nil Thread.current[@http.request_key] - assert_nil Thread.current[@http.timeout_key] - end - - def test_shutdown_not_started - ssl_conns - - c = basic_connection - def c.finish() raise IOError end - - conns[0]["#{@uri.host}:#{@uri.port}"] = c - - @http.shutdown - - assert_empty Thread.current[@http.generation_key] - assert_empty Thread.current[@http.ssl_generation_key] - - assert_nil Thread.current[@http.request_key] - assert_nil Thread.current[@http.timeout_key] + assert c.http.finished?, 'last-generation connection must be finished' + refute c2.http.finished?, 'present generation connection must not be finished' end - def test_shutdown_ssl + def test_ssl skip 'OpenSSL is missing' unless HAVE_OPENSSL - @uri = URI 'https://example' - - @http.connection_for @uri - - @http.shutdown - - assert_empty ssl_conns - end - - def test_shutdown_thread - t = Thread.new do - c = connection - conns - ssl_conns - - reqs - - Thread.stop - - c - end - - Thread.pass until t.status == 'sleep' - - c = connection - - @http.shutdown t + @http.verify_callback = :callback + c = Net::HTTP.new 'localhost', 80 - refute c.finished? + @http.ssl c - t.run - assert t.value.finished? - assert_empty t[@http.generation_key] - assert_empty t[@http.ssl_generation_key] - assert_nil t[@http.request_key] - assert_nil t[@http.timeout_key] + assert c.use_ssl? + assert_equal OpenSSL::SSL::VERIFY_PEER, c.verify_mode + assert_kind_of OpenSSL::X509::Store, c.cert_store + assert_nil c.verify_callback end - def test_ssl + def test_ssl_ca_file skip 'OpenSSL is missing' unless HAVE_OPENSSL + @http.ca_file = 'ca_file' @http.verify_callback = :callback c = Net::HTTP.new 'localhost', 80 @@ -1563,14 +1410,13 @@ class TestNetHttpPersistent < Minitest::Test assert c.use_ssl? assert_equal OpenSSL::SSL::VERIFY_PEER, c.verify_mode - assert_kind_of OpenSSL::X509::Store, c.cert_store - assert_nil c.verify_callback + assert_equal :callback, c.verify_callback end - def test_ssl_ca_file + def test_ssl_ca_path skip 'OpenSSL is missing' unless HAVE_OPENSSL - @http.ca_file = 'ca_file' + @http.ca_path = 'ca_path' @http.verify_callback = :callback c = Net::HTTP.new 'localhost', 80 @@ -1667,23 +1513,11 @@ class TestNetHttpPersistent < Minitest::Test end end - def test_ssl_cleanup - skip 'OpenSSL is missing' unless HAVE_OPENSSL - - uri1 = URI.parse 'https://one.example' - - c1 = @http.connection_for uri1 - - touts[c1.object_id] = Time.now - reqs[c1.object_id] = 5 - - @http.reconnect_ssl - - @http.ssl_cleanup @http.ssl_generation + def test_ssl_timeout_equals + @http.ssl_timeout = :ssl_timeout - assert_empty ssl_conns - assert_empty touts - assert_empty reqs # sanity check, performed by #finish + assert_equal :ssl_timeout, @http.ssl_timeout + assert_equal 1, @http.ssl_generation end def test_ssl_version_equals @@ -1691,10 +1525,11 @@ class TestNetHttpPersistent < Minitest::Test assert_equal :ssl_version, @http.ssl_version assert_equal 1, @http.ssl_generation - end unless RUBY_1 + end def test_start c = basic_connection + c = c.http @http.socket_options << [Socket::SOL_SOCKET, Socket::SO_KEEPALIVE, 1] @http.debug_output = $stderr @@ -1722,6 +1557,13 @@ class TestNetHttpPersistent < Minitest::Test assert_equal 1, @http.ssl_generation end + def test_verify_depth_equals + @http.verify_depth = :verify_depth + + assert_equal :verify_depth, @http.verify_depth + assert_equal 1, @http.ssl_generation + end + def test_verify_mode_equals @http.verify_mode = :verify_mode diff --git a/test/test_net_http_persistent_ssl_reuse.rb b/test/test_net_http_persistent_ssl_reuse.rb deleted file mode 100644 index b231899707c696bf1700150a2c4b0deec0275357..0000000000000000000000000000000000000000 --- a/test/test_net_http_persistent_ssl_reuse.rb +++ /dev/null @@ -1,112 +0,0 @@ -require 'rubygems' -require 'minitest/autorun' -require 'net/http/persistent' -have_ssl = - begin - require 'openssl' - require 'webrick' - require 'webrick/ssl' - true - rescue LoadError - false - end - -## -# This test is based on (and contains verbatim code from) the Net::HTTP tests -# in ruby - -class TestNetHttpPersistentSSLReuse < Minitest::Test - - class NullWriter - def <<(s) end - def puts(*args) end - def print(*args) end - def printf(*args) end - end - - def setup - @name = OpenSSL::X509::Name.parse 'CN=localhost/DC=localdomain' - - @key = OpenSSL::PKey::RSA.new 1024 - - @cert = OpenSSL::X509::Certificate.new - @cert.version = 2 - @cert.serial = 0 - @cert.not_before = Time.now - @cert.not_after = Time.now + 300 - @cert.public_key = @key.public_key - @cert.subject = @name - @cert.issuer = @name - - @cert.sign @key, OpenSSL::Digest::SHA1.new - - @host = 'localhost' - @port = 10082 - - config = { - :BindAddress => @host, - :Port => @port, - :Logger => WEBrick::Log.new(NullWriter.new), - :AccessLog => [], - :ShutDownSocketWithoutClose => true, - :ServerType => Thread, - :SSLEnable => true, - :SSLCertificate => @cert, - :SSLPrivateKey => @key, - :SSLStartImmediately => true, - } - - @server = WEBrick::HTTPServer.new config - - @server.mount_proc '/' do |req, res| - res.body = "ok" - end - - @server.start - - begin - TCPSocket.open(@host, @port).close - rescue Errno::ECONNREFUSED - sleep 0.2 - n_try_max -= 1 - raise 'cannot spawn server; give up' if n_try_max < 0 - retry - end - end - - def teardown - if @server then - @server.shutdown - sleep 0.01 until @server.status == :Stop - end - end - - def test_ssl_connection_reuse - store = OpenSSL::X509::Store.new - store.add_cert @cert - - @http = Net::HTTP::Persistent::SSLReuse.new @host, @port - @http.cert_store = store - @http.ssl_version = :SSLv3 if @http.respond_to? :ssl_version= - @http.use_ssl = true - @http.verify_mode = OpenSSL::SSL::VERIFY_PEER - - @http.start - @http.get '/' - @http.finish - - @http.start - @http.get '/' - @http.finish - - @http.start - @http.get '/' - - socket = @http.instance_variable_get :@socket - ssl_socket = socket.io - - assert ssl_socket.session_reused? - end - -end if have_ssl - diff --git a/test/test_net_http_persistent_timed_stack_multi.rb b/test/test_net_http_persistent_timed_stack_multi.rb new file mode 100644 index 0000000000000000000000000000000000000000..ae3e34ba70fdecafb580354fc8a0efc2f5d826ab --- /dev/null +++ b/test/test_net_http_persistent_timed_stack_multi.rb @@ -0,0 +1,151 @@ +require 'minitest/autorun' +require 'net/http/persistent' + +class TestNetHttpPersistentTimedStackMulti < Minitest::Test + + class Connection + attr_reader :host + + def initialize(host) + @host = host + end + end + + def setup + @stack = Net::HTTP::Persistent::TimedStackMulti.new { Object.new } + end + + def test_empty_eh + stack = Net::HTTP::Persistent::TimedStackMulti.new(1) { Object.new } + + refute_empty stack + + popped = stack.pop + + assert_empty stack + + stack.push connection_args: popped + + refute_empty stack + end + + def test_length + stack = Net::HTTP::Persistent::TimedStackMulti.new(1) { Object.new } + + assert_equal 1, stack.length + + popped = stack.pop + + assert_equal 0, stack.length + + stack.push connection_args: popped + + assert_equal 1, stack.length + end + + def test_pop + object = Object.new + @stack.push object + + popped = @stack.pop + + assert_same object, popped + end + + def test_pop_empty + e = assert_raises Timeout::Error do + @stack.pop timeout: 0 + end + + assert_equal 'Waited 0 sec', e.message + end + + def test_pop_full + stack = Net::HTTP::Persistent::TimedStackMulti.new(1) { Object.new } + + popped = stack.pop + + refute_nil popped + assert_empty stack + end + + def test_pop_wait + thread = Thread.start do + @stack.pop + end + + Thread.pass while thread.status == 'run' + + object = Object.new + + @stack.push object + + assert_same object, thread.value + end + + def test_pop_shutdown + @stack.shutdown { } + + assert_raises ConnectionPool::PoolShuttingDownError do + @stack.pop + end + end + + def test_push + stack = Net::HTTP::Persistent::TimedStackMulti.new(1) { Object.new } + + conn = stack.pop + + stack.push connection_args: conn + + refute_empty stack + end + + def test_push_shutdown + called = [] + + @stack.shutdown do |object| + called << object + end + + @stack.push connection_args: Object.new + + refute_empty called + assert_empty @stack + end + + def test_shutdown + @stack.push connection_args: Object.new + + called = [] + + @stack.shutdown do |object| + called << object + end + + refute_empty called + assert_empty @stack + end + + def test_pop_recycle + stack = Net::HTTP::Persistent::TimedStackMulti.new(2) { |host| Connection.new(host) } + + a_conn = stack.pop connection_args: 'a.example' + stack.push a_conn, connection_args: 'a.example' + + b_conn = stack.pop connection_args: 'b.example' + stack.push b_conn, connection_args: 'b.example' + + c_conn = stack.pop connection_args: 'c.example' + + assert_equal 'c.example', c_conn.host + + stack.push c_conn, connection_args: 'c.example' + + recreated = stack.pop connection_args: 'a.example' + + refute_same a_conn, recreated + end + +end +