moduleExconclassConnectionattr_reader:connection,:proxyCR_NL="\r\n"HTTP_1_1=" HTTP/1.1\r\n"FORCE_ENC=CR_NL.respond_to?(:force_encoding)# Initializes a new Connection instance# @param [String] url The destination URL# @param [Hash<Symbol, >] params One or more optional params# @option params [String] :body Default text to be sent over a socket. Only used if :body absent in Connection#request params# @option params [Hash<Symbol, String>] :headers The default headers to supply in a request. Only used if params[:headers] is not supplied to Connection#request# @option params [String] :host The destination host's reachable DNS name or IP, in the form of a String# @option params [String] :path Default path; appears after 'scheme://host:port/'. Only used if params[:path] is not supplied to Connection#request# @option params [Fixnum] :port The port on which to connect, to the destination host# @option params [Hash] :query Default query; appended to the 'scheme://host:port/path/' in the form of '?key=value'. Will only be used if params[:query] is not supplied to Connection#request# @option params [String] :scheme The protocol; 'https' causes OpenSSL to be used# @option params [String] :proxy Proxy server; e.g. 'http://myproxy.com:8888'# @option params [Fixnum] :retry_limit Set how many times we'll retry a failed request. (Default 4)definitialize(url,params={})uri=URI.parse(url)@connection={:connect_timeout=>60,:headers=>{},:host=>uri.host,:mock=>Excon.mock,:path=>uri.path,:port=>uri.port.to_s,:query=>uri.query,:read_timeout=>60,:scheme=>uri.scheme,:write_timeout=>60}.merge!(params)# use proxy from the environment if presentifENV.has_key?('http_proxy')@proxy=setup_proxy(ENV['http_proxy'])elsifparams.has_key?(:proxy)@proxy=setup_proxy(params[:proxy])endself.retry_limit=params[:retry_limit]||DEFAULT_RETRY_LIMITif@connection[:scheme]=='https'# use https_proxy if that has been specifiedifENV.has_key?('https_proxy')@proxy=setup_proxy(ENV['https_proxy'])endendif@proxy@connection[:headers]['Proxy-Connection']||='Keep-Alive'end@socket_key=''<<@connection[:host]<<':'<<@connection[:port]resetend# Sends the supplied request to the destination host.# @yield [chunk] @see Response#self.parse# @param [Hash<Symbol, >] params One or more optional params, override defaults set in Connection.new# @option params [String] :body text to be sent over a socket# @option params [Hash<Symbol, String>] :headers The default headers to supply in a request# @option params [String] :host The destination host's reachable DNS name or IP, in the form of a String# @option params [String] :path appears after 'scheme://host:port/'# @option params [Fixnum] :port The port on which to connect, to the destination host# @option params [Hash] :query appended to the 'scheme://host:port/path/' in the form of '?key=value'# @option params [String] :scheme The protocol; 'https' causes OpenSSL to be useddefrequest(params,&block)begin# connection has defaults, merge in new params to overrideparams=@connection.merge(params)params[:headers]=@connection[:headers].merge(params[:headers]||{})params[:headers]['Host']||=''<<params[:host]<<':'<<params[:port]# if path is empty or doesn't start with '/', insert oneunlessparams[:path][0,1]=='/'params[:path].insert(0,'/')endunlessparams[:mock]socket.params=paramselseforstub,responseinExcon.stubs# all specified non-headers params match and no headers were specified or all specified headers matchif(stub.keys-[:headers]).all?{|key|stub[key]==params[key]}&&(!stub.has_key?(:headers)||stub[:headers].keys.all?{|key|stub[:headers][key]==params[:headers][key]})response_attributes=caseresponsewhenProcresponse.call(params)elseresponseendifblock_given?&&response_attributes.has_key?(:body)body=response_attributes.delete(:body)content_length=remaining=body.bytesizei=0whilei<body.lengthyield(body[i,CHUNK_SIZE],[remaining-CHUNK_SIZE,0].max,content_length)remaining-=CHUNK_SIZEi+=CHUNK_SIZEendendreturnExcon::Response.new(response_attributes)endend# if we reach here no stubs matchedraise(Excon::Errors::StubNotFound.new('no stubs matched '<<params.inspect))end# start with "METHOD /path"request=params[:method].to_s.upcase<<' 'if@proxyrequest<<params[:scheme]<<'://'<<params[:host]<<':'<<params[:port]endrequest<<params[:path]# add query to path, if there is onecaseparams[:query]whenStringrequest<<'?'<<params[:query]whenHashrequest<<'?'forkey,valuesinparams[:query]ifvalues.nil?request<<key.to_s<<'&'elseforvaluein[*values]request<<key.to_s<<'='<<CGI.escape(value.to_s)<<'&'endendendrequest.chop!# remove trailing '&'end# finish first line with "HTTP/1.1\r\n"request<<HTTP_1_1# calculate content length and set to handle non-asciiunlessparams[:headers].has_key?('Content-Length')params[:headers]['Content-Length']=caseparams[:body]whenFileparams[:body].binmodeFile.size(params[:body])whenStringifFORCE_ENCparams[:body].force_encoding('BINARY')endparams[:body].lengthelse0endend# add headers to requestforkey,valuesinparams[:headers]forvaluein[*values]request<<key.to_s<<': '<<value.to_s<<CR_NLendend# add additional "\r\n" to indicate end of headersrequest<<CR_NL# write out the request, sans bodysocket.write(request)# write out the bodyifparams[:body]ifparams[:body].is_a?(String)socket.write(params[:body])elsewhilechunk=params[:body].read(CHUNK_SIZE)socket.write(chunk)endendend# read the responseresponse=Excon::Response.parse(socket,params,&block)ifresponse.headers['Connection']=='close'resetendresponserescueExcon::Errors::StubNotFound=>stub_not_foundraise(stub_not_found)rescue=>socket_errorresetraise(Excon::Errors::SocketError.new(socket_error))endifparams.has_key?(:expects)&&![*params[:expects]].include?(response.status)resetraise(Excon::Errors.status_error(params,response))elseresponseendrescue=>request_errorifparams[:idempotent]&&[Excon::Errors::SocketError,Excon::Errors::HTTPStatusError].any?{|ex|request_error.kind_of?ex}retries_remaining||=retry_limitretries_remaining-=1ifretries_remaining>0ifparams[:body].respond_to?(:pos=)params[:body].pos=0endretryelseraise(request_error)endelseraise(request_error)endensureifparams[:body]&¶ms[:body].respond_to?(:close)params[:body].closeendenddefreset(old_socket=sockets.delete(@socket_key))&&old_socket.closeend# Generate HTTP request verb methodsExcon::HTTP_VERBS.eachdo|method|eval<<-DEF
def #{method}(params={}, &block)
request(params.merge!(:method => :#{method}), &block)
end
DEFendattr_writer:retry_limitdefretry_limit@retry_limit||=DEFAULT_RETRY_LIMITendprivatedefsocketsockets[@socket_key]||=if@connection[:scheme]=='https'Excon::SSLSocket.new(@connection,@proxy)elseExcon::Socket.new(@connection,@proxy)endenddefsocketsThread.current[:_excon_sockets]||={}enddefsetup_proxy(proxy)uri=URI.parse(proxy)unlessuri.hostanduri.portanduri.schemeraiseExcon::Errors::ProxyParseError,"Proxy is invalid"end{:host=>uri.host,:port=>uri.port,:scheme=>uri.scheme}endendend