# frozen_string_literal: truerequire'cgi'require'net/http'require'net/https'require'delegate'require'thread'require'logger'require_relative'patches'Seahorse::Client::NetHttp::Patches.apply!moduleSeahorsemoduleClient# @api privatemoduleNetHttpclassConnectionPool@pools_mutex=Mutex.new@pools={}@default_logger=Logger.new($stdout)OPTIONS={http_proxy: nil,http_open_timeout: 15,http_read_timeout: 60,http_idle_timeout: 5,http_continue_timeout: 1,http_wire_trace: false,logger: nil,ssl_verify_peer: true,ssl_ca_bundle: nil,ssl_ca_directory: nil,ssl_ca_store: nil,ssl_timeout: nil}# @api privatedefinitialize(options={})OPTIONS.each_pairdo|opt_name,default_value|value=options[opt_name].nil??default_value:options[opt_name]instance_variable_set("@#{opt_name}",value)end@pool_mutex=Mutex.new@pool={}endOPTIONS.keys.eachdo|attr_name|attr_reader(attr_name)endaliashttp_wire_trace?http_wire_tracealiasssl_verify_peer?ssl_verify_peer# Makes an HTTP request, yielding a Net::HTTPResponse object.## pool.request(URI.parse('http://domain'), Net::HTTP::Get.new('/')) do |resp|# puts resp.code # status code# puts resp.to_h.inspect # dump the headers# puts resp.body# end## @param [URI::HTTP, URI::HTTPS] endpoint The HTTP(S) endpoint# to connect to (e.g. 'https://domain.com').## @param [Net::HTTPRequest] request The request to make. This can be# any request object from Net::HTTP (e.g. Net::HTTP::Get,# Net::HTTP::POST, etc).## @yieldparam [Net::HTTPResponse] net_http_response## @return (see #session_for)defrequest(endpoint,request,&block)session_for(endpoint)do|http|yield(http.request(request))endend# @param [URI::HTTP, URI::HTTPS] endpoint The HTTP(S) endpoint# to connect to (e.g. 'https://domain.com').## @yieldparam [Net::HTTPSession] session## @return [nil]defsession_for(endpoint,&block)endpoint=remove_path_and_query(endpoint)session=nil# attempt to recycle an already open session@pool_mutex.synchronizedo_cleanif@pool.key?(endpoint)session=@pool[endpoint].shiftendendbeginsession||=start_session(endpoint)session.read_timeout=http_read_timeoutsession.continue_timeout=http_continue_timeoutifsession.respond_to?(:continue_timeout=)yield(session)rescuesession.finishifsessionraiseelse# No error raised? Good, check the session into the pool.@pool_mutex.synchronizedo@pool[endpoint]=[]unless@pool.key?(endpoint)@pool[endpoint]<<sessionendendnilend# @return [Integer] Returns the count of sessions currently in the# pool, not counting those currently in use.defsize@pool_mutex.synchronizedosize=0@pool.each_pairdo|endpoint,sessions|size+=sessions.sizeendsizeendend# Removes stale http sessions from the pool (that have exceeded# the idle timeout).# @return [nil]defclean!@pool_mutex.synchronize{_clean}nilend# Closes and removes all sessions from the pool.# If empty! is called while there are outstanding requests they may# get checked back into the pool, leaving the pool in a non-empty# state.# @return [nil]defempty!@pool_mutex.synchronizedo@pool.each_pairdo|endpoint,sessions|sessions.each(&:finish)end@pool.clearendnilendprivatedefremove_path_and_query(endpoint)endpoint.dup.tapdo|e|e.path=''e.query=nilend.to_sendclass<<self# Returns a connection pool constructed from the given options.# Calling this method twice with the same options will return# the same pool.## @option options [URI::HTTP,String] :http_proxy A proxy to send# requests through. Formatted like 'http://proxy.com:123'.## @option options [Float] :http_open_timeout (15) The number of# seconds to wait when opening an HTTP session before raising a# `Timeout::Error`.## @option options [Float] :http_read_timeout (60) The default# number of seconds to wait for response data. This value can be# safely set per-request on the session yielded by {#session_for}.## @option options [Float] :http_idle_timeout (5) The number of# seconds a connection is allowed to sit idle before it is# considered stale. Stale connections are closed and removed# from the pool before making a request.## @option options [Float] :http_continue_timeout (1) The number of# seconds to wait for a 100-continue response before sending the# request body. This option has no effect unless the request has# "Expect" header set to "100-continue". Defaults to `nil` which# disables this behaviour. This value can safely be set per# request on the session yielded by {#session_for}.## @option options [Float] :ssl_timeout (nil) Sets the SSL timeout# in seconds.## @option options [Boolean] :http_wire_trace (false) When `true`,# HTTP debug output will be sent to the `:logger`.## @option options [Logger] :logger Where debug output is sent.# Defaults to `nil` when `:http_wire_trace` is `false`.# Defaults to `Logger.new($stdout)` when `:http_wire_trace` is# `true`.## @option options [Boolean] :ssl_verify_peer (true) When `true`,# SSL peer certificates are verified when establishing a# connection.## @option options [String] :ssl_ca_bundle Full path to the SSL# certificate authority bundle file that should be used when# verifying peer certificates. If you do not pass# `:ssl_ca_bundle` or `:ssl_ca_directory` the system default# will be used if available.## @option options [String] :ssl_ca_directory Full path of the# directory that contains the unbundled SSL certificate# authority files for verifying peer certificates. If you do# not pass `:ssl_ca_bundle` or `:ssl_ca_directory` the# system default will be used if available.## @return [ConnectionPool]defforoptions={}options=pool_options(options)@pools_mutex.synchronizedo@pools[options]||=new(options)endend# @return [Array<ConnectionPool>] Returns a list of the# constructed connection pools.defpools@pools_mutex.synchronizedo@pools.valuesendendprivate# Filters an option hash, merging in default values.# @return [Hash]defpool_optionsoptionswire_trace=!!options[:http_wire_trace]logger=options[:logger]||@default_loggerifwire_traceverify_peer=options.key?(:ssl_verify_peer)?!!options[:ssl_verify_peer]:true{:http_proxy=>URI.parse(options[:http_proxy].to_s),:http_continue_timeout=>options[:http_continue_timeout],:http_open_timeout=>options[:http_open_timeout]||15,:http_idle_timeout=>options[:http_idle_timeout]||5,:http_read_timeout=>options[:http_read_timeout]||60,:http_wire_trace=>wire_trace,:logger=>logger,:ssl_verify_peer=>verify_peer,:ssl_ca_bundle=>options[:ssl_ca_bundle],:ssl_ca_directory=>options[:ssl_ca_directory],:ssl_ca_store=>options[:ssl_ca_store],:ssl_timeout=>options[:ssl_timeout]}endendprivate# Extract the parts of the http_proxy URI# @return [Array(String)]defhttp_proxy_partsreturn[http_proxy.host,http_proxy.port,(http_proxy.user&&CGI::unescape(http_proxy.user)),(http_proxy.password&&CGI::unescape(http_proxy.password))]end# Starts and returns a new HTTP(S) session.# @param [String] endpoint# @return [Net::HTTPSession]defstart_sessionendpointendpoint=URI.parse(endpoint)args=[]args<<endpoint.hostargs<<endpoint.portargs+=http_proxy_partshttp=ExtendedSession.new(Net::HTTP.new(*args.compact))http.set_debug_output(logger)ifhttp_wire_trace?http.open_timeout=http_open_timeouthttp.keep_alive_timeout=http_idle_timeoutifhttp.respond_to?(:keep_alive_timeout=)ifendpoint.scheme=='https'http.use_ssl=truehttp.ssl_timeout=ssl_timeoutifssl_verify_peer?http.verify_mode=OpenSSL::SSL::VERIFY_PEERhttp.ca_file=ssl_ca_bundleifssl_ca_bundlehttp.ca_path=ssl_ca_directoryifssl_ca_directoryhttp.cert_store=ssl_ca_storeifssl_ca_storeelsehttp.verify_mode=OpenSSL::SSL::VERIFY_NONEendelsehttp.use_ssl=falseendhttp.starthttpend# Removes stale sessions from the pool. This method *must* be called# @note **Must** be called behind a `@pool_mutex` synchronize block.def_cleannow=Aws::Util.monotonic_milliseconds@pool.each_pairdo|endpoint,sessions|sessions.delete_ifdo|session|ifsession.last_used.nil?ornow-session.last_used>http_idle_timeout*1000session.finishtrueendendendend# Helper methods extended onto Net::HTTPSession objects opened by the# connection pool.# @api privateclassExtendedSession<Delegatordefinitialize(http)super(http)@http=httpend# @return [Integer,nil]attr_reader:last_useddef__getobj__@httpenddef__setobj__(obj)@http=objend# Sends the request and tracks that this session has been used.defrequest(*args,&block)@http.request(*args,&block)@last_used=Aws::Util.monotonic_millisecondsend# Attempts to close/finish the session without raising an error.deffinish@http.finishrescueIOErrornilendendendendendend