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?
- 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_connect_error
- on_error
- on_io_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 = @max_concurrent_requests = 49 @parser = @sibling = @coalesced_connection = @altsvc_connection = 50 @ping_timer = @family = @io = @ssl_session = 51 @timeout = @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 @no_more_requests_counter = 0 66 67 if @options.io 68 # if there's an already open IO, get its 69 # peer address, and force-initiate the parser 70 transition(:already_open) 71 @io = build_socket 72 parser 73 else 74 transition(:idle) 75 end 76 self.addresses = @options.addresses if @options.addresses 77 end
Public Instance methods
# File lib/httpx/connection.rb 93 def addresses 94 @io && @io.addresses 95 end
this is a semi-private method, to be used by the resolver to initiate the io object.
# File lib/httpx/connection.rb 85 def addresses=(addrs) 86 if @io 87 @io.add_addresses(addrs) 88 else 89 @io = build_socket(addrs) 90 end 91 end
# File lib/httpx/connection.rb 97 def addresses? 98 @io && @io.addresses? 99 end
# File lib/httpx/connection.rb 216 def call 217 case @state 218 when :idle 219 connect 220 221 # when opening the tcp or ssl socket fails 222 return if @state == :closed 223 224 consume 225 when :closed 226 return if @pending.empty? 227 228 # there are pending requests to send, restart the state machine. 229 idling 230 231 # @fiber-switch-guard 232 # fiber may have switch after ensuring that @io is closed. 233 return unless @state == :idle 234 235 call 236 when :closing 237 consume 238 transition(:closed) 239 240 # @fiber-switch-guard 241 # fiber may have switch while closing @io. 242 return if @state == :closed && 243 # only remain here if there are pending requests. 244 @pending.empty? 245 246 call 247 when :open 248 consume 249 end 250 nil 251 rescue IOError => e 252 @write_buffer.clear 253 on_io_error(e) 254 rescue StandardError => e 255 @write_buffer.clear 256 on_error(e) 257 rescue Exception => e # rubocop:disable Lint/RescueException 258 force_close(true) 259 raise e 260 end
# File lib/httpx/connection.rb 262 def close 263 transition(:active) if @state == :inactive 264 265 @parser.close if @parser 266 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 183 def connecting? 184 @state == :idle 185 end
# File lib/httpx/connection.rb 387 def deactivate 388 transition(:inactive) 389 end
disconnects from the current session it’s attached to
# File lib/httpx/connection.rb 422 def disconnect 423 return if @exhausted # it'll reset 424 425 return unless (current_session = @current_session) && (current_selector = @current_selector) 426 427 @current_session = @current_selector = nil 428 429 current_session.deselect_connection(self, current_selector, @cloned) 430 end
bypasses state machine rules while setting the connection in the :closed state.
# File lib/httpx/connection.rb 287 def force_close(delete_pending = false) 288 force_purge 289 return unless @state == :closed 290 291 if delete_pending 292 @pending.clear 293 elsif (parser = @parser) 294 enqueue_pending_requests_from_parser(parser) 295 end 296 297 return unless @pending.empty? 298 299 disconnect 300 emit(:force_closed, delete_pending) 301 end
bypasses the state machine to force closing of connections still connecting. only used for Happy Eyeballs v2.
# File lib/httpx/connection.rb 305 def force_reset(cloned = false) 306 @state = :closing 307 @cloned = cloned 308 transition(:closed) 309 end
# File lib/httpx/connection.rb 413 def handle_connect_error(error) 414 return on_error(error) unless @sibling && @sibling.connecting? 415 416 @sibling.merge(self) 417 418 force_reset(true) 419 end
# File lib/httpx/connection.rb 395 def handle_socket_timeout(interval) 396 error = OperationTimeoutError.new(interval, "timed out while waiting on select") 397 error.set_backtrace(caller) 398 on_error(error) 399 end
# File lib/httpx/connection.rb 370 def idling 371 purge_after_closed 372 373 return unless @state == :closed 374 375 @write_buffer.clear 376 transition(:idle) 377 return unless @parser 378 379 enqueue_pending_requests_from_parser(parser) 380 @parser = nil 381 end
# File lib/httpx/connection.rb 187 def inflight? 188 @parser && ( 189 # parser may be dealing with other requests (possibly started from a different fiber) 190 !@parser.empty? || 191 # connection may be doing connection termination handshake 192 !@write_buffer.empty? 193 ) 194 end
:nocov:
# File lib/httpx/connection.rb 466 def inspect 467 "#<#{self.class}:#{object_id} " \ 468 "@origin=#{@origin} " \ 469 "@state=#{@state} " \ 470 "@pending=#{@pending.size} " \ 471 "@io=#{@io}>" 472 end
# File lib/httpx/connection.rb 196 def interests 197 # connecting 198 if connecting? 199 connect 200 201 return @io.interests if connecting? 202 end 203 204 return @parser.interests if @parser 205 206 nil 207 rescue StandardError => e 208 on_error(e) 209 nil 210 end
# File lib/httpx/connection.rb 177 def io_connected? 178 return @coalesced_connection.io_connected? if @coalesced_connection 179 180 @io && @io.state == :connected 181 end
# File lib/httpx/connection.rb 101 def match?(uri, options) 102 return false if !used? && (@state == :closing || @state == :closed) 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.connection_options_match?(options) 111 end
# File lib/httpx/connection.rb 149 def merge(connection) 150 @origins |= connection.instance_variable_get(:@origins) 151 if @ssl_session.nil? && connection.ssl_session 152 @ssl_session = connection.ssl_session 153 @io.session_new_cb do |sess| 154 @ssl_session = sess 155 end if @io 156 end 157 connection.purge_pending do |req| 158 req.transition(:idle) 159 send(req) 160 true 161 end 162 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_match?(connection.options) 122 end
# File lib/httpx/connection.rb 432 def on_connect_error(e) 433 # connect errors, exit gracefully 434 error = ConnectionError.new(e.message) 435 error.set_backtrace(e.backtrace) 436 handle_connect_error(error) if connecting? 437 force_close 438 end
# File lib/httpx/connection.rb 445 def on_error(error, request = nil) 446 if error.is_a?(OperationTimeoutError) 447 448 # inactive connections do not contribute to the select loop, therefore 449 # they should not fail due to such errors. 450 return if @state == :inactive 451 452 if @timeout 453 @timeout -= error.timeout 454 return unless @timeout <= 0 455 456 @timeout = nil 457 end 458 459 error = error.to_connection_error if connecting? 460 end 461 handle_error(error, request) 462 reset 463 end
# File lib/httpx/connection.rb 440 def on_io_error(e) 441 on_error(e) 442 force_close(true) 443 end
# File lib/httpx/connection.rb 391 def open? 392 @state == :open || @state == :inactive 393 end
# File lib/httpx/connection.rb 164 def purge_pending(&block) 165 pendings = [] 166 if @parser 167 pending = @parser.pending 168 @inflight -= pending.size 169 pendings << pending 170 end 171 pendings << @pending 172 pendings.each do |pending| 173 pending.reject!(&block) 174 end 175 end
# File lib/httpx/connection.rb 311 def reset 312 return if @state == :closing || @state == :closed 313 314 # do not reset a connection which may have restarted back to :idle, such when the parser resets 315 # (example: HTTP/1 parser disabling pipelining) 316 return if @state == :idle && @pending.any? 317 318 if @ping_timer 319 @ping_timer.cancel 320 @ping_timer = nil 321 end 322 323 parser = @parser 324 325 if parser && parser.respond_to?(:max_concurrent_requests) 326 # if connection being reset has at some downgraded the number of concurrent 327 # requests, such as in the case where an attempt to use HTTP/1 pipelining failed, 328 # keep that information around. 329 @max_concurrent_requests = parser.max_concurrent_requests 330 end 331 332 transition(:closing) 333 334 transition(:closed) 335 end
# File lib/httpx/connection.rb 337 def send(request) 338 return @coalesced_connection.send(request) if @coalesced_connection 339 340 if @parser && !@write_buffer.full? 341 if @response_received_at && @keep_alive_timeout && 342 Utils.elapsed_time(@response_received_at) > @keep_alive_timeout 343 # when pushing a request into an existing connection, we have to check whether there 344 # is the possibility that the connection might have extended the keep alive timeout. 345 # for such cases, we want to ping for availability before deciding to shovel requests. 346 log(level: 3) { "keep alive timeout expired, pinging connection..." } 347 @pending << request 348 transition(:active) if @state == :inactive 349 request.ping! 350 ping(request) 351 return 352 end 353 354 send_request_to_parser(request) 355 else 356 @pending << request 357 end 358 end
# File lib/httpx/connection.rb 401 def sibling=(connection) 402 @sibling = connection 403 404 return unless connection 405 406 @main_sibling = connection.sibling.nil? 407 408 return unless @main_sibling 409 410 connection.sibling = self 411 end
# File lib/httpx/connection.rb 268 def terminate 269 case @state 270 when :idle 271 purge_after_closed 272 273 # @fiber-switch-guard 274 if @io.can_disconnect? && @pending.empty? 275 disconnect 276 return 277 end 278 when :closed 279 @connected_at = nil 280 end 281 282 close 283 end
# File lib/httpx/connection.rb 360 def timeout 361 return if @state == :closed || @state == :inactive 362 363 return @timeout if @timeout 364 365 return @options.timeout[:connect_timeout] if @state == :idle 366 367 @options.timeout[:operation_timeout] 368 end