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_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 212 def call 213 case @state 214 when :idle 215 connect 216 217 # when opening the tcp or ssl socket fails 218 return if @state == :closed 219 220 consume 221 when :closed 222 return 223 when :closing 224 consume 225 transition(:closed) 226 when :open 227 consume 228 end 229 nil 230 rescue StandardError => e 231 @write_buffer.clear 232 on_error(e) 233 rescue Exception => e # rubocop:disable Lint/RescueException 234 force_close(true) 235 raise e 236 end
# File lib/httpx/connection.rb 238 def close 239 transition(:active) if @state == :inactive 240 241 @parser.close if @parser 242 end
coalescable connections need to be mergeable! but internally, mergeable? is called before coalescable?
# File lib/httpx/connection.rb 137 def coalescable?(connection) 138 if @io.protocol == "h2" && 139 @origin.scheme == "https" && 140 connection.origin.scheme == "https" && 141 @io.can_verify_peer? 142 @io.verify_hostname(connection.origin.host) 143 else 144 @origin == connection.origin 145 end 146 end
coalesces self into connection.
# File lib/httpx/connection.rb 124 def coalesce!(connection) 125 @coalesced_connection = connection 126 127 close_sibling 128 connection.merge(self) 129 end
# File lib/httpx/connection.rb 131 def coalesced? 132 @coalesced_connection 133 end
# File lib/httpx/connection.rb 179 def connecting? 180 @state == :idle 181 end
# File lib/httpx/connection.rb 333 def deactivate 334 transition(:inactive) 335 end
disconnects from the current session it’s attached to
# File lib/httpx/connection.rb 368 def disconnect 369 return if @exhausted # it'll reset 370 371 return unless (current_session = @current_session) && (current_selector = @current_selector) 372 373 @current_session = @current_selector = nil 374 375 current_session.deselect_connection(self, current_selector, @cloned) 376 end
bypasses state machine rules while setting the connection in the :closed state.
# File lib/httpx/connection.rb 258 def force_close(delete_pending = false) 259 if delete_pending 260 @pending.clear 261 elsif (parser = @parser) 262 enqueue_pending_requests_from_parser(parser) 263 end 264 return if @state == :closed 265 266 @state = :closed 267 @write_buffer.clear 268 purge_after_closed 269 disconnect 270 emit(:force_closed, delete_pending) 271 end
bypasses the state machine to force closing of connections still connecting. only used for Happy Eyeballs v2.
# File lib/httpx/connection.rb 275 def force_reset(cloned = false) 276 @state = :closing 277 @cloned = cloned 278 transition(:closed) 279 end
# File lib/httpx/connection.rb 359 def handle_connect_error(error) 360 return on_error(error) unless @sibling && @sibling.connecting? 361 362 @sibling.merge(self) 363 364 force_reset(true) 365 end
# File lib/httpx/connection.rb 341 def handle_socket_timeout(interval) 342 error = OperationTimeoutError.new(interval, "timed out while waiting on select") 343 error.set_backtrace(caller) 344 on_error(error) 345 end
# File lib/httpx/connection.rb 322 def idling 323 purge_after_closed 324 @write_buffer.clear 325 transition(:idle) 326 @parser = nil if @parser 327 end
# File lib/httpx/connection.rb 183 def inflight? 184 @parser && ( 185 # parser may be dealing with other requests (possibly started from a different fiber) 186 !@parser.empty? || 187 # connection may be doing connection termination handshake 188 !@write_buffer.empty? 189 ) 190 end
:nocov:
# File lib/httpx/connection.rb 397 def inspect 398 "#<#{self.class}:#{object_id} " \ 399 "@origin=#{@origin} " \ 400 "@state=#{@state} " \ 401 "@pending=#{@pending.size} " \ 402 "@io=#{@io}>" 403 end
# File lib/httpx/connection.rb 192 def interests 193 # connecting 194 if connecting? 195 connect 196 197 return @io.interests if connecting? 198 end 199 200 return @parser.interests if @parser 201 202 nil 203 rescue StandardError => e 204 on_error(e) 205 nil 206 end
# File lib/httpx/connection.rb 173 def io_connected? 174 return @coalesced_connection.io_connected? if @coalesced_connection 175 176 @io && @io.state == :connected 177 end
# File lib/httpx/connection.rb 100 def match?(uri, options) 101 return false if !used? && (@state == :closing || @state == :closed) 102 103 @origins.include?(uri.origin) && 104 # if there is more than one origin to match, it means that this connection 105 # was the result of coalescing. To prevent blind trust in the case where the 106 # origin came from an ORIGIN frame, we're going to verify the hostname with the 107 # SSL certificate 108 (@origins.size == 1 || @origin == uri.origin || (@io.is_a?(SSL) && @io.verify_hostname(uri.host))) && 109 @options == options 110 end
# File lib/httpx/connection.rb 148 def merge(connection) 149 @origins |= connection.instance_variable_get(:@origins) 150 if @ssl_session.nil? && connection.ssl_session 151 @ssl_session = connection.ssl_session 152 @io.session_new_cb do |sess| 153 @ssl_session = sess 154 end if @io 155 end 156 connection.purge_pending do |req| 157 send(req) 158 end 159 end
# File lib/httpx/connection.rb 112 def mergeable?(connection) 113 return false if @state == :closing || @state == :closed || !@io 114 115 return false unless connection.addresses 116 117 ( 118 (open? && @origin == connection.origin) || 119 !(@io.addresses & (connection.addresses || [])).empty? 120 ) && @options == connection.options 121 end
# File lib/httpx/connection.rb 378 def on_error(error, request = nil) 379 if error.is_a?(OperationTimeoutError) 380 381 # inactive connections do not contribute to the select loop, therefore 382 # they should not fail due to such errors. 383 return if @state == :inactive 384 385 if @timeout 386 @timeout -= error.timeout 387 return unless @timeout <= 0 388 end 389 390 error = error.to_connection_error if connecting? 391 end 392 handle_error(error, request) 393 reset 394 end
# File lib/httpx/connection.rb 337 def open? 338 @state == :open || @state == :inactive 339 end
# File lib/httpx/connection.rb 161 def purge_pending(&block) 162 pendings = [] 163 if @parser 164 @inflight -= @parser.pending.size 165 pendings << @parser.pending 166 end 167 pendings << @pending 168 pendings.each do |pending| 169 pending.reject!(&block) 170 end 171 end
# File lib/httpx/connection.rb 281 def reset 282 return if @state == :closing || @state == :closed 283 284 transition(:closing) 285 286 transition(:closed) 287 end
# File lib/httpx/connection.rb 289 def send(request) 290 return @coalesced_connection.send(request) if @coalesced_connection 291 292 if @parser && !@write_buffer.full? 293 if @response_received_at && @keep_alive_timeout && 294 Utils.elapsed_time(@response_received_at) > @keep_alive_timeout 295 # when pushing a request into an existing connection, we have to check whether there 296 # is the possibility that the connection might have extended the keep alive timeout. 297 # for such cases, we want to ping for availability before deciding to shovel requests. 298 log(level: 3) { "keep alive timeout expired, pinging connection..." } 299 @pending << request 300 transition(:active) if @state == :inactive 301 parser.ping 302 request.ping! 303 return 304 end 305 306 send_request_to_parser(request) 307 else 308 @pending << request 309 end 310 end
# File lib/httpx/connection.rb 347 def sibling=(connection) 348 @sibling = connection 349 350 return unless connection 351 352 @main_sibling = connection.sibling.nil? 353 354 return unless @main_sibling 355 356 connection.sibling = self 357 end
# File lib/httpx/connection.rb 244 def terminate 245 case @state 246 when :idle 247 purge_after_closed 248 disconnect 249 when :closed 250 @connected_at = nil 251 end 252 253 close 254 end
# File lib/httpx/connection.rb 312 def timeout 313 return if @state == :closed || @state == :inactive 314 315 return @timeout if @timeout 316 317 return @options.timeout[:connect_timeout] if @state == :idle 318 319 @options.timeout[:operation_timeout] 320 end