# frozen_string_literal: truerequire'net/https'require'openssl'moduleSeahorsemoduleClient# @api privatemoduleNetHttp# The default HTTP handler for Seahorse::Client. This is based on# the Ruby's `Net::HTTP`.classHandler<Client::Handler# @api privateclassTruncatedBodyError<IOErrordefinitialize(bytes_expected,bytes_received)msg="http response body truncated, expected #{bytes_expected} "\"bytes, received #{bytes_received} bytes"super(msg)endendNETWORK_ERRORS=[SocketError,EOFError,IOError,Timeout::Error,Errno::ECONNABORTED,Errno::ECONNRESET,Errno::EPIPE,Errno::EINVAL,Errno::ETIMEDOUT,OpenSSL::SSL::SSLError,Errno::EHOSTUNREACH,Errno::ECONNREFUSED,Net::HTTPFatalError# for proxy connection failures]# does not exist in Ruby 1.9.3ifOpenSSL::SSL.const_defined?(:SSLErrorWaitReadable)NETWORK_ERRORS<<OpenSSL::SSL::SSLErrorWaitReadableend# @api privateDNS_ERROR_MESSAGES=['getaddrinfo: nodename nor servname provided, or not known',# MacOS'getaddrinfo: Name or service not known'# GNU]# Raised when a {Handler} cannot construct a `Net::HTTP::Request`# from the given http verb.classInvalidHttpVerbError<StandardError;end# @param [RequestContext] context# @return [Response]defcall(context)transmit(context.config,context.http_request,context.http_response)Response.new(context: context)end# @param [Configuration] config# @return [ConnectionPool]defpool_for(config)ConnectionPool.for(pool_options(config))endprivatedeferror_message(req,error)iferror.is_a?(SocketError)&&DNS_ERROR_MESSAGES.include?(error.message)host=req.endpoint.host"unable to connect to `#{host}`; SocketError: #{error.message}"elseerror.messageendend# @param [Configuration] config# @param [Http::Request] req# @param [Http::Response] resp# @return [void]deftransmit(config,req,resp)session(config,req)do|http|# Monkey patch default content-type set by Net::HTTPThread.current[:net_http_skip_default_content_type]=truehttp.request(build_net_request(req))do|net_resp|status_code=net_resp.code.to_iheaders=extract_headers(net_resp)bytes_received=0resp.signal_headers(status_code,headers)net_resp.read_bodydo|chunk|bytes_received+=chunk.bytesizeresp.signal_data(chunk)endcomplete_response(req,resp,bytes_received)endendrescue*NETWORK_ERRORS=>error# these are retryableerror=NetworkingError.new(error,error_message(req,error))resp.signal_error(error)rescue=>error# not retryableresp.signal_error(error)ensure# ensure we turn off monkey patch in case of errorThread.current[:net_http_skip_default_content_type]=nilenddefcomplete_response(req,resp,bytes_received)ifshould_verify_bytes?(req,resp)verify_bytes_received(resp,bytes_received)elseresp.signal_doneendenddefshould_verify_bytes?(req,resp)req.http_method!='HEAD'&&resp.headers['content-length']enddefverify_bytes_received(resp,bytes_received)bytes_expected=resp.headers['content-length'].to_iifbytes_expected==bytes_receivedresp.signal_doneelseerror=TruncatedBodyError.new(bytes_expected,bytes_received)resp.signal_error(NetworkingError.new(error,error.message))endenddefsession(config,req,&block)pool_for(config).session_for(req.endpoint)do|http|# Ruby 2.5, can disable retries for idempotent operations# avoid patching for Ruby 2.5 for disable retryhttp.max_retries=0ifhttp.respond_to?(:max_retries)http.read_timeout=config.http_read_timeoutyield(http)endend# Extracts the {ConnectionPool} configuration options.# @param [Configuration] config# @return [Hash]defpool_options(config)ConnectionPool::OPTIONS.keys.inject({})do|opts,opt|opts[opt]=config.send(opt)optsendend# Constructs and returns a Net::HTTP::Request object from# a {Http::Request}.# @param [Http::Request] request# @return [Net::HTTP::Request]defbuild_net_request(request)request_class=net_http_request_class(request)req=request_class.new(request.endpoint.request_uri,headers(request))# Net::HTTP adds a default Content-Type when a body is present.# Set the body stream when it has an unknown size or when it is > 0.if!request.body.respond_to?(:size)||(request.body.respond_to?(:size)&&request.body.size>0)req.body_stream=request.bodyendreqend# @param [Http::Request] request# @raise [InvalidHttpVerbError]# @return Returns a base `Net::HTTP::Request` class, e.g.,# `Net::HTTP::Get`, `Net::HTTP::Post`, etc.defnet_http_request_class(request)Net::HTTP.const_get(request.http_method.capitalize)rescueNameErrormsg="`#{request.http_method}` is not a valid http verb"raiseInvalidHttpVerbError,msgend# @param [Http::Request] request# @return [Hash] Returns a vanilla hash of headers to send with the# HTTP request.defheaders(request)# Net::HTTP adds a default header for accept-encoding (2.0.0+).# Setting a default empty value defeats this.## Removing this is necessary for most services to not break request# signatures as well as dynamodb crc32 checks (these fail if the# response is gzipped).headers={'accept-encoding'=>''}request.headers.each_pairdo|key,value|headers[key]=valueendheadersend# @param [Net::HTTP::Response] response# @return [Hash<String, String>]defextract_headers(response)response.to_hash.inject({})do|headers,(k,v)|headers[k]=v.firstheadersendendendendendend