Class: HTTPX::TCP
- Inherits:
-
Object
- Object
- HTTPX::TCP
- Includes:
- Loggable
- Defined in:
- lib/httpx/io/tcp.rb,
sig/io/tcp.rbs
Constant Summary
Constants included from Loggable
Loggable::COLORS, Loggable::USE_DEBUG_LOG
Instance Attribute Summary collapse
-
#addresses ⇒ Array[Resolver::Entry]
readonly
Returns the value of attribute addresses.
-
#interests ⇒ io_interests
readonly
Returns the value of attribute interests.
-
#ip ⇒ Resolver::Entry?
(also: #host)
readonly
Returns the value of attribute ip.
-
#port ⇒ Integer
readonly
Returns the value of attribute port.
-
#state ⇒ Symbol
readonly
Returns the value of attribute state.
Instance Method Summary collapse
- #add_addresses(addrs) ⇒ void
-
#addresses? ⇒ Boolean
eliminates expired entries and returns whether there are still any left.
- #build_socket ⇒ Socket
-
#can_disconnect? ⇒ Boolean
signals that the connection that contains this IO can be checked back into the pool.
- #close ⇒ void
- #closed? ⇒ Boolean
- #connect ⇒ void
- #connected? ⇒ Boolean
- #do_transition(nextstate) ⇒ void
-
#initialize(origin, addresses, options) ⇒ TCP
constructor
TODO: lift when https://github.com/ruby/rbs/issues/1497 fixed.
-
#inspect ⇒ ::String
:nocov:.
- #log_transition_state(nextstate) ⇒ String
- #protocol ⇒ String
- #read(size, buffer) ⇒ 0, ...
- #socket ⇒ Object
- #to_io ⇒ IO
- #transition(nextstate) ⇒ void
-
#try_connect ⇒ void
:nocov:.
- #write(buffer) ⇒ Integer?
Methods included from Loggable
#log, #log_exception, log_identifiers, #log_redact, #log_redact_body, #log_redact_headers
Constructor Details
#initialize(origin, addresses, options) ⇒ TCP
TODO: lift when https://github.com/ruby/rbs/issues/1497 fixed
30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 |
# File 'sig/io/tcp.rbs', line 30 def initialize(origin, addresses, ) @state = :idle @keep_open = false @addresses = [] @ip_index = -1 @ip = nil @hostname = origin.host @options = @fallback_protocol = @options.fallback_protocol @port = origin.port @interests = :w if (io = @options.io) io = case io when Hash io[origin.] else io end raise Error, "Given IO objects do not match the request authority" unless io # @type var io: TCPSocket | OpenSSL::SSL::SSLSocket _, _, _, ip = io.addr @io = io @addresses << (@ip = Resolver::Entry.new(ip)) @keep_open = true @state = :connected else add_addresses(addresses) end @ip_index = @addresses.size - 1 end |
Instance Attribute Details
#addresses ⇒ Array[Resolver::Entry] (readonly)
Returns the value of attribute addresses.
11 12 13 |
# File 'lib/httpx/io/tcp.rb', line 11 def addresses @addresses end |
#interests ⇒ io_interests (readonly)
Returns the value of attribute interests.
11 12 13 |
# File 'lib/httpx/io/tcp.rb', line 11 def interests @interests end |
#ip ⇒ Resolver::Entry? (readonly) Also known as: host
Returns the value of attribute ip.
11 12 13 |
# File 'lib/httpx/io/tcp.rb', line 11 def ip @ip end |
#port ⇒ Integer (readonly)
Returns the value of attribute port.
11 12 13 |
# File 'lib/httpx/io/tcp.rb', line 11 def port @port end |
#state ⇒ Symbol (readonly)
Returns the value of attribute state.
11 12 13 |
# File 'lib/httpx/io/tcp.rb', line 11 def state @state end |
Instance Method Details
#add_addresses(addrs) ⇒ void
This method returns an undefined value.
53 54 55 56 57 58 59 60 61 62 63 64 |
# File 'lib/httpx/io/tcp.rb', line 53 def add_addresses(addrs) return if addrs.empty? ip_index = @ip_index || (@addresses.size - 1) if addrs.first.ipv6? # should be the next in line @addresses = [*@addresses[0, ip_index], *addrs, *@addresses[ip_index..-1]] else @addresses.unshift(*addrs) end @ip_index += addrs.size end |
#addresses? ⇒ Boolean
eliminates expired entries and returns whether there are still any left.
67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 |
# File 'lib/httpx/io/tcp.rb', line 67 def addresses? prev_addr_size = @addresses.size @addresses.delete_if(&:expired?).sort! do |addr1, addr2| if addr1.ipv6? addr2.ipv6? ? 0 : 1 else addr2.ipv6? ? -1 : 0 end end @ip_index = @addresses.size - 1 if prev_addr_size != @addresses.size @addresses.any? end |
#build_socket ⇒ Socket
235 236 237 238 |
# File 'lib/httpx/io/tcp.rb', line 235 def build_socket @ip = @addresses[@ip_index] Socket.new(@ip.family, :STREAM, 0) end |
#can_disconnect? ⇒ Boolean
signals that the connection that contains this IO can be checked back into the pool. that includes sockets opened outside of the scope of the session, or closed IOs.
210 211 212 |
# File 'lib/httpx/io/tcp.rb', line 210 def can_disconnect? @keep_open || @state == :closed end |
#close ⇒ void
This method returns an undefined value.
184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 |
# File 'lib/httpx/io/tcp.rb', line 184 def close return if @keep_open # mark tcp as closed, so that it can be disconnected. # this bypasses the state machine API as not not allow the transition # from idle to closed in normal circumstances. @state = :closed if @state == :idle return if closed? begin @io.close rescue IOError => e log { "error closing socket" } log { e.(highlight: false) } ensure # @fiber-switch-guard # ensure that all :closed IOs don't leave dangling sockets # behind. This may happen in a fiber scheduler scenario where # connection is reused across fibers. transition(:closed) if @io.closed? end end |
#closed? ⇒ Boolean
218 219 220 |
# File 'lib/httpx/io/tcp.rb', line 218 def closed? @state == :idle || @state == :closed end |
#connect ⇒ void
This method returns an undefined value.
91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 |
# File 'lib/httpx/io/tcp.rb', line 91 def connect return unless closed? if @addresses.empty? # an idle connection trying to connect with no available addresses is a connection # out of the initial context which is back to the DNS resolution loop. This may # happen in a fiber-aware context where a connection reconnects with expired addresses, # and context is passed back to a fiber on the same connection while waiting for the # DNS answer. log { "tried connecting while resolving, skipping..." } return end if !@io || @io.closed? transition(:idle) @io = build_socket end try_connect rescue Errno::EHOSTUNREACH, Errno::ENETUNREACH => e @ip_index -= 1 raise e if @ip_index.negative? log { "failed connecting to #{@ip} (#{e.}), evict from cache and trying next..." } @options.resolver_cache.evict(@hostname, @ip) @io = build_socket retry rescue Errno::ECONNREFUSED, Errno::EADDRNOTAVAIL, SocketError, IOError => e @ip_index -= 1 raise e if @ip_index.negative? log { "failed connecting to #{@ip} (#{e.}), trying next..." } @io = build_socket retry rescue Errno::ETIMEDOUT => e @ip_index -= 1 raise ConnectTimeoutError.new(@options.timeout[:connect_timeout], e.) if @ip_index.negative? log { "failed connecting to #{@ip} (#{e.}), trying next..." } @io = build_socket retry end |
#connected? ⇒ Boolean
214 215 216 |
# File 'lib/httpx/io/tcp.rb', line 214 def connected? @state == :connected end |
#do_transition(nextstate) ⇒ void
This method returns an undefined value.
251 252 253 254 |
# File 'lib/httpx/io/tcp.rb', line 251 def do_transition(nextstate) log(level: 1) { log_transition_state(nextstate) } @state = nextstate end |
#inspect ⇒ ::String
:nocov:
223 224 225 226 227 228 229 230 |
# File 'lib/httpx/io/tcp.rb', line 223 def inspect "#<#{self.class}:#{object_id} " \ "#{@ip}:#{@port} " \ "@state=#{@state} " \ "@hostname=#{@hostname} " \ "@addresses=#{@addresses} " \ "@state=#{@state}>" end |
#log_transition_state(nextstate) ⇒ String
256 257 258 259 260 |
# File 'lib/httpx/io/tcp.rb', line 256 def log_transition_state(nextstate) label = host label = "#{label}(##{@io.fileno})" if nextstate == :connected "#{label} #{@state} -> #{nextstate}" end |
#protocol ⇒ String
87 88 89 |
# File 'lib/httpx/io/tcp.rb', line 87 def protocol @fallback_protocol end |
#read(size, buffer) ⇒ 0, ...
161 162 163 164 165 166 167 168 169 170 171 |
# File 'lib/httpx/io/tcp.rb', line 161 def read(size, buffer) ret = @io.read_nonblock(size, buffer, exception: false) if ret == :wait_readable buffer.clear return 0 end return if ret.nil? log { "READ: #{buffer.bytesize} bytes..." } buffer.bytesize end |
#socket ⇒ Object
49 50 51 |
# File 'lib/httpx/io/tcp.rb', line 49 def socket @io end |
#to_io ⇒ IO
83 84 85 |
# File 'lib/httpx/io/tcp.rb', line 83 def to_io @io.to_io end |
#transition(nextstate) ⇒ void
This method returns an undefined value.
240 241 242 243 244 245 246 247 248 249 |
# File 'lib/httpx/io/tcp.rb', line 240 def transition(nextstate) case nextstate # when :idle when :connected return unless @state == :idle when :closed return unless @state == :connected end do_transition(nextstate) end |
#try_connect ⇒ void
This method returns an undefined value.
:nocov:
45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 |
# File 'sig/io/tcp.rbs', line 45 def try_connect ret = @io.connect_nonblock(Socket.sockaddr_in(@port, @ip.to_s), exception: false) log(level: 3, color: :cyan) { "TCP CONNECT: #{ret}..." } case ret when :wait_readable @interests = :r return when :wait_writable @interests = :w return end transition(:connected) @interests = :w rescue Errno::EALREADY @interests = :w end |
#write(buffer) ⇒ Integer?
173 174 175 176 177 178 179 180 181 182 |
# File 'lib/httpx/io/tcp.rb', line 173 def write(buffer) siz = @io.write_nonblock(buffer, exception: false) return 0 if siz == :wait_writable return if siz.nil? log { "WRITE: #{siz} bytes..." } buffer.shift!(siz) siz end |