moduleExconclassSocketincludeUtilsextendForwardableattr_accessor:datadefparamsExcon.display_warning('Excon::Socket#params is deprecated use Excon::Socket#data instead.')@dataenddefparams=(new_params)Excon.display_warning('Excon::Socket#params= is deprecated use Excon::Socket#data= instead.')@data=new_paramsendattr_reader:remote_ipdef_delegators(:@socket,:close,:close)definitialize(data={})@data=data@nonblock=data[:nonblock]@read_buffer=''@eof=falseconnectenddefread(max_length=nil)if@eofreturnmax_length?nil:''elsif@nonblockbeginifmax_lengthuntil@read_buffer.length>=max_length@read_buffer<<@socket.read_nonblock(max_length-@read_buffer.length)endelsewhiletrue@read_buffer<<@socket.read_nonblock(@data[:chunk_size])endendrescueOpenSSL::SSL::SSLError=>erroriferror.message=='read would block'ifIO.select([@socket],nil,nil,@data[:read_timeout])retryelseraise(Excon::Errors::Timeout.new("read timeout reached"))endelseraise(error)endrescueErrno::EAGAIN,Errno::EWOULDBLOCK,IO::WaitReadableifIO.select([@socket],nil,nil,@data[:read_timeout])retryelseraise(Excon::Errors::Timeout.new("read timeout reached"))endrescueEOFError@eof=trueendifmax_lengthif@read_buffer.empty?nil# EOF met at beginningelse@read_buffer.slice!(0,max_length)endelse# read until EOFError, so return everything@read_buffer.slice!(0,@read_buffer.length)endelsebeginTimeout.timeout(@data[:read_timeout])do@socket.read(max_length)endrescueTimeout::ErrorraiseExcon::Errors::Timeout.new('read timeout reached')endendenddefreadlineif@eofraiseEOFError,'end of file reached'elseline=''if@nonblockwhilechar=read(1)line<<charbreakifchar==$/endraiseEOFError,'end of file reached'ifline.empty?elsebeginTimeout.timeout(@data[:read_timeout])doline=@socket.readlineendrescueTimeout::ErrorraiseExcon::Errors::Timeout.new('read timeout reached')endendlineendenddefwrite(data)if@nonblockifFORCE_ENCdata.force_encoding('BINARY')endwhiletruewritten=nilbegin# I wish that this API accepted a start position, then we wouldn't# have to slice data when there is a short write.written=@socket.write_nonblock(data)rescueOpenSSL::SSL::SSLError,Errno::EAGAIN,Errno::EWOULDBLOCK,IO::WaitWritable=>erroriferror.is_a?(OpenSSL::SSL::SSLError)&&error.message!='write would block'raiseerrorelseifIO.select(nil,[@socket],nil,@data[:write_timeout])retryelseraiseExcon::Errors::Timeout.new('write timeout reached')endendend# Fast, common case.breakifwritten==data.size# This takes advantage of the fact that most ruby implementations# have Copy-On-Write strings. Thusly why requesting a subrange# of data, we actually don't copy data because the new string# simply references a subrange of the original.data=data[written,data.size]endelsebeginTimeout.timeout(@data[:write_timeout])do@socket.write(data)endrescueTimeout::Errorraise(Excon::Errors::Timeout.new('write timeout reached'))endendenddeflocal_port::Socket.unpack_sockaddr_in(@socket.to_io.getsockname)[0]rescueArgumentError=>eraiseunlesse.message=='not an AF_INET/AF_INET6 sockaddr'enddeflocal_address::Socket.unpack_sockaddr_in(@socket.to_io.getsockname)[1]rescueArgumentError=>eraiseunlesse.message=='not an AF_INET/AF_INET6 sockaddr'endprivatedefconnect@socket=nilexception=nilif@data[:proxy]family=@data[:proxy][:family]||::Socket::Constants::AF_UNSPECargs=[@data[:proxy][:host],@data[:proxy][:port],family,::Socket::Constants::SOCK_STREAM]elsefamily=@data[:family]||::Socket::Constants::AF_UNSPECargs=[@data[:host],@data[:port],family,::Socket::Constants::SOCK_STREAM]endifRUBY_VERSION>='1.9.2'&&defined?(RUBY_ENGINE)&&RUBY_ENGINE=='ruby'args<<nil<<nil<<false# no reverse lookupendaddrinfo=::Socket.getaddrinfo(*args)addrinfo.eachdo|_,port,_,ip,a_family,s_type|@remote_ip=ip# nonblocking connectbeginsockaddr=::Socket.sockaddr_in(port,ip)socket=::Socket.new(a_family,s_type,0)if@data[:reuseaddr]socket.setsockopt(::Socket::Constants::SOL_SOCKET,::Socket::Constants::SO_REUSEADDR,true)ifdefined?(::Socket::Constants::SO_REUSEPORT)socket.setsockopt(::Socket::Constants::SOL_SOCKET,::Socket::Constants::SO_REUSEPORT,true)endendbeginTimeout.timeout(@data[:connect_timeout])doif@nonblocksocket.connect_nonblock(sockaddr)elsesocket.connect(sockaddr)endendrescueTimeout::ErrorraiseExcon::Errors::Timeout.new('connect timeout reached')end@socket=socketbreakrescueErrno::EINPROGRESSunlessIO.select(nil,[socket],nil,@data[:connect_timeout])raise(Excon::Errors::Timeout.new("connect timeout reached"))endbeginsocket.connect_nonblock(sockaddr)@socket=socketbreakrescueErrno::EISCONN@socket=socketbreakrescueSystemCallError=>exceptionsocket.closerescuenilnextendrescueSystemCallError=>exceptionsocket.closerescuenilifsocketnextendendunless@socket# this will be our last encountered exceptionraiseexceptionendif@data[:tcp_nodelay]@socket.setsockopt(::Socket::IPPROTO_TCP,::Socket::TCP_NODELAY,true)endendendend