The Connection can be watched for IO events.
It contains the io object to read/write from, and knows what to do when it can.
It defers connecting until absolutely necessary. Connection should be triggered from the IO selector (until then, any request will be queued).
A connection boots up its parser after connection is established. All pending requests will be redirected there after connection.
A connection can be prevented from closing by the parser, that is, if there are pending requests. This will signal that the connection was prematurely closed, due to a possible number of conditions:
-
Remote peer closed the connection (“Connection: close”);
-
Remote peer doesn’t support pipelining;
A connection may also route requests for a different host for which the io was connected to, provided that the IP is the same and the port and scheme as well. This will allow to share the same socket to send HTTP/2 requests to different hosts.
Methods
Public Class
Public Instance
- addresses
- addresses=
- addresses?
- call
- close
- coalescable?
- coalesce!
- coalesced?
- connecting?
- create_idle
- current_selector
- current_session
- deactivate
- disconnect
- family
- force_close
- force_reset
- handle_connect_error
- handle_socket_timeout
- idling
- inflight?
- inspect
- interests
- io
- io_connected?
- match?
- merge
- mergeable?
- on_error
- open?
- options
- origin
- origins
- peer
- pending
- purge_pending
- reset
- send
- sibling=
- state
- terminate
- timeout
- to_io
- type
- used?
Protected Instance
Classes and Modules
Attributes
| current_selector | [W] | |
| current_session | [RW] | |
| family | [RW] | |
| io | [R] | |
| options | [R] | |
| origin | [R] | |
| origins | [R] | |
| pending | [R] | |
| sibling | [R] | |
| ssl_session | [R] | |
| state | [R] | |
| type | [R] |
Public Class methods
# File lib/httpx/connection.rb 47 def initialize(uri, options) 48 @current_session = @current_selector = 49 @parser = @sibling = @coalesced_connection = @altsvc_connection = 50 @family = @io = @ssl_session = @timeout = 51 @connected_at = @response_received_at = nil 52 53 @exhausted = @cloned = @main_sibling = false 54 55 @options = Options.new(options) 56 @type = initialize_type(uri, @options) 57 @origins = [uri.origin] 58 @origin = Utils.to_uri(uri.origin) 59 @window_size = @options.window_size 60 @read_buffer = Buffer.new(@options.buffer_size) 61 @write_buffer = Buffer.new(@options.buffer_size) 62 @pending = [] 63 @inflight = 0 64 @keep_alive_timeout = @options.timeout[:keep_alive_timeout] 65 66 if @options.io 67 # if there's an already open IO, get its 68 # peer address, and force-initiate the parser 69 transition(:already_open) 70 @io = build_socket 71 parser 72 else 73 transition(:idle) 74 end 75 self.addresses = @options.addresses if @options.addresses 76 end
Public Instance methods
# File lib/httpx/connection.rb 92 def addresses 93 @io && @io.addresses 94 end
this is a semi-private method, to be used by the resolver to initiate the io object.
# File lib/httpx/connection.rb 84 def addresses=(addrs) 85 if @io 86 @io.add_addresses(addrs) 87 else 88 @io = build_socket(addrs) 89 end 90 end
# File lib/httpx/connection.rb 96 def addresses? 97 @io && @io.addresses? 98 end
# File lib/httpx/connection.rb 217 def call 218 case @state 219 when :idle 220 connect 221 222 # when opening the tcp or ssl socket fails 223 return if @state == :closed 224 225 consume 226 when :closed 227 return 228 when :closing 229 consume 230 transition(:closed) 231 when :open 232 consume 233 end 234 nil 235 rescue StandardError => e 236 @write_buffer.clear 237 on_error(e) 238 rescue Exception => e # rubocop:disable Lint/RescueException 239 force_close(true) 240 raise e 241 end
# File lib/httpx/connection.rb 243 def close 244 transition(:active) if @state == :inactive 245 246 @parser.close if @parser 247 end
coalescable connections need to be mergeable! but internally, mergeable? is called before coalescable?
# File lib/httpx/connection.rb 138 def coalescable?(connection) 139 if @io.protocol == "h2" && 140 @origin.scheme == "https" && 141 connection.origin.scheme == "https" && 142 @io.can_verify_peer? 143 @io.verify_hostname(connection.origin.host) 144 else 145 @origin == connection.origin 146 end 147 end
coalesces self into connection.
# File lib/httpx/connection.rb 125 def coalesce!(connection) 126 @coalesced_connection = connection 127 128 close_sibling 129 connection.merge(self) 130 end
# File lib/httpx/connection.rb 132 def coalesced? 133 @coalesced_connection 134 end
# File lib/httpx/connection.rb 184 def connecting? 185 @state == :idle 186 end
# File lib/httpx/connection.rb 149 def create_idle(options = {}) 150 self.class.new(@origin, @options.merge(options)) 151 end
# File lib/httpx/connection.rb 338 def deactivate 339 transition(:inactive) 340 end
disconnects from the current session it’s attached to
# File lib/httpx/connection.rb 373 def disconnect 374 return if @exhausted # it'll reset 375 376 return unless (current_session = @current_session) && (current_selector = @current_selector) 377 378 @current_session = @current_selector = nil 379 380 current_session.deselect_connection(self, current_selector, @cloned) 381 end
bypasses state machine rules while setting the connection in the :closed state.
# File lib/httpx/connection.rb 263 def force_close(delete_pending = false) 264 if delete_pending 265 @pending.clear 266 elsif (parser = @parser) 267 enqueue_pending_requests_from_parser(parser) 268 end 269 return if @state == :closed 270 271 @state = :closed 272 @write_buffer.clear 273 purge_after_closed 274 disconnect 275 emit(:force_closed, delete_pending) 276 end
bypasses the state machine to force closing of connections still connecting. only used for Happy Eyeballs v2.
# File lib/httpx/connection.rb 280 def force_reset(cloned = false) 281 @state = :closing 282 @cloned = cloned 283 transition(:closed) 284 end
# File lib/httpx/connection.rb 364 def handle_connect_error(error) 365 return on_error(error) unless @sibling && @sibling.connecting? 366 367 @sibling.merge(self) 368 369 force_reset(true) 370 end
# File lib/httpx/connection.rb 346 def handle_socket_timeout(interval) 347 error = OperationTimeoutError.new(interval, "timed out while waiting on select") 348 error.set_backtrace(caller) 349 on_error(error) 350 end
# File lib/httpx/connection.rb 327 def idling 328 purge_after_closed 329 @write_buffer.clear 330 transition(:idle) 331 @parser = nil if @parser 332 end
# File lib/httpx/connection.rb 188 def inflight? 189 @parser && ( 190 # parser may be dealing with other requests (possibly started from a different fiber) 191 !@parser.empty? || 192 # connection may be doing connection termination handshake 193 !@write_buffer.empty? 194 ) 195 end
:nocov:
# File lib/httpx/connection.rb 402 def inspect 403 "#<#{self.class}:#{object_id} " \ 404 "@origin=#{@origin} " \ 405 "@state=#{@state} " \ 406 "@pending=#{@pending.size} " \ 407 "@io=#{@io}>" 408 end
# File lib/httpx/connection.rb 197 def interests 198 # connecting 199 if connecting? 200 connect 201 202 return @io.interests if connecting? 203 end 204 205 return @parser.interests if @parser 206 207 nil 208 rescue StandardError => e 209 on_error(e) 210 nil 211 end
# File lib/httpx/connection.rb 178 def io_connected? 179 return @coalesced_connection.io_connected? if @coalesced_connection 180 181 @io && @io.state == :connected 182 end
# File lib/httpx/connection.rb 100 def match?(uri, options) 101 return false if !used? && (@state == :closing || @state == :closed) 102 103 ( 104 @origins.include?(uri.origin) && 105 # if there is more than one origin to match, it means that this connection 106 # was the result of coalescing. To prevent blind trust in the case where the 107 # origin came from an ORIGIN frame, we're going to verify the hostname with the 108 # SSL certificate 109 (@origins.size == 1 || @origin == uri.origin || (@io.is_a?(SSL) && @io.verify_hostname(uri.host))) 110 ) && @options == options 111 end
# File lib/httpx/connection.rb 153 def merge(connection) 154 @origins |= connection.instance_variable_get(:@origins) 155 if @ssl_session.nil? && connection.ssl_session 156 @ssl_session = connection.ssl_session 157 @io.session_new_cb do |sess| 158 @ssl_session = sess 159 end if @io 160 end 161 connection.purge_pending do |req| 162 send(req) 163 end 164 end
# File lib/httpx/connection.rb 113 def mergeable?(connection) 114 return false if @state == :closing || @state == :closed || !@io 115 116 return false unless connection.addresses 117 118 ( 119 (open? && @origin == connection.origin) || 120 !(@io.addresses & (connection.addresses || [])).empty? 121 ) && @options == connection.options 122 end
# File lib/httpx/connection.rb 383 def on_error(error, request = nil) 384 if error.is_a?(OperationTimeoutError) 385 386 # inactive connections do not contribute to the select loop, therefore 387 # they should not fail due to such errors. 388 return if @state == :inactive 389 390 if @timeout 391 @timeout -= error.timeout 392 return unless @timeout <= 0 393 end 394 395 error = error.to_connection_error if connecting? 396 end 397 handle_error(error, request) 398 reset 399 end
# File lib/httpx/connection.rb 342 def open? 343 @state == :open || @state == :inactive 344 end
# File lib/httpx/connection.rb 166 def purge_pending(&block) 167 pendings = [] 168 if @parser 169 @inflight -= @parser.pending.size 170 pendings << @parser.pending 171 end 172 pendings << @pending 173 pendings.each do |pending| 174 pending.reject!(&block) 175 end 176 end
# File lib/httpx/connection.rb 286 def reset 287 return if @state == :closing || @state == :closed 288 289 transition(:closing) 290 291 transition(:closed) 292 end
# File lib/httpx/connection.rb 294 def send(request) 295 return @coalesced_connection.send(request) if @coalesced_connection 296 297 if @parser && !@write_buffer.full? 298 if @response_received_at && @keep_alive_timeout && 299 Utils.elapsed_time(@response_received_at) > @keep_alive_timeout 300 # when pushing a request into an existing connection, we have to check whether there 301 # is the possibility that the connection might have extended the keep alive timeout. 302 # for such cases, we want to ping for availability before deciding to shovel requests. 303 log(level: 3) { "keep alive timeout expired, pinging connection..." } 304 @pending << request 305 transition(:active) if @state == :inactive 306 parser.ping 307 request.ping! 308 return 309 end 310 311 send_request_to_parser(request) 312 else 313 @pending << request 314 end 315 end
# File lib/httpx/connection.rb 352 def sibling=(connection) 353 @sibling = connection 354 355 return unless connection 356 357 @main_sibling = connection.sibling.nil? 358 359 return unless @main_sibling 360 361 connection.sibling = self 362 end
# File lib/httpx/connection.rb 249 def terminate 250 case @state 251 when :idle 252 purge_after_closed 253 disconnect 254 when :closed 255 @connected_at = nil 256 end 257 258 close 259 end
# File lib/httpx/connection.rb 317 def timeout 318 return if @state == :closed || @state == :inactive 319 320 return @timeout if @timeout 321 322 return @options.timeout[:connect_timeout] if @state == :idle 323 324 @options.timeout[:operation_timeout] 325 end