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!
- connecting?
- create_idle
- current_selector
- current_session
- deactivate
- disconnect
- family
- force_reset
- handle_connect_error
- handle_socket_timeout
- idling
- inflight?
- inspect
- interests
- io
- io_connected?
- match?
- merge
- mergeable?
- 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 49 def initialize(uri, options) 50 @current_session = @current_selector = 51 @parser = @sibling = @coalesced_connection = 52 @family = @io = @ssl_session = @timeout = 53 @connected_at = @response_received_at = nil 54 55 @exhausted = @cloned = @main_sibling = false 56 57 @options = Options.new(options) 58 @type = initialize_type(uri, @options) 59 @origins = [uri.origin] 60 @origin = Utils.to_uri(uri.origin) 61 @window_size = @options.window_size 62 @read_buffer = Buffer.new(@options.buffer_size) 63 @write_buffer = Buffer.new(@options.buffer_size) 64 @pending = [] 65 @inflight = 0 66 @keep_alive_timeout = @options.timeout[:keep_alive_timeout] 67 68 on(:error, &method(:on_error)) 69 if @options.io 70 # if there's an already open IO, get its 71 # peer address, and force-initiate the parser 72 transition(:already_open) 73 @io = build_socket 74 parser 75 else 76 transition(:idle) 77 end 78 on(:close) do 79 next if @exhausted # it'll reset 80 81 # may be called after ":close" above, so after the connection has been checked back in. 82 # next unless @current_session 83 84 next unless @current_session 85 86 @current_session.deselect_connection(self, @current_selector, @cloned) 87 end 88 on(:terminate) do 89 next if @exhausted # it'll reset 90 91 current_session = @current_session 92 current_selector = @current_selector 93 94 # may be called after ":close" above, so after the connection has been checked back in. 95 next unless current_session && current_selector 96 97 current_session.deselect_connection(self, current_selector) 98 end 99 100 on(:altsvc) do |alt_origin, origin, alt_params| 101 build_altsvc_connection(alt_origin, origin, alt_params) 102 end 103 104 self.addresses = @options.addresses if @options.addresses 105 end
Public Instance methods
# File lib/httpx/connection.rb 121 def addresses 122 @io && @io.addresses 123 end
this is a semi-private method, to be used by the resolver to initiate the io object.
# File lib/httpx/connection.rb 113 def addresses=(addrs) 114 if @io 115 @io.add_addresses(addrs) 116 else 117 @io = build_socket(addrs) 118 end 119 end
# File lib/httpx/connection.rb 125 def addresses? 126 @io && @io.addresses? 127 end
# File lib/httpx/connection.rb 242 def call 243 case @state 244 when :idle 245 connect 246 247 # when opening the tcp or ssl socket fails 248 return if @state == :closed 249 250 consume 251 when :closed 252 return 253 when :closing 254 consume 255 transition(:closed) 256 when :open 257 consume 258 end 259 nil 260 rescue StandardError => e 261 @write_buffer.clear 262 emit(:error, e) 263 raise e 264 end
# File lib/httpx/connection.rb 266 def close 267 transition(:active) if @state == :inactive 268 269 @parser.close if @parser 270 end
coalescable connections need to be mergeable! but internally, mergeable?
is called before coalescable?
# File lib/httpx/connection.rb 163 def coalescable?(connection) 164 if @io.protocol == "h2" && 165 @origin.scheme == "https" && 166 connection.origin.scheme == "https" && 167 @io.can_verify_peer? 168 @io.verify_hostname(connection.origin.host) 169 else 170 @origin == connection.origin 171 end 172 end
coalesces self
into connection
.
# File lib/httpx/connection.rb 154 def coalesce!(connection) 155 @coalesced_connection = connection 156 157 close_sibling 158 connection.merge(self) 159 end
# File lib/httpx/connection.rb 209 def connecting? 210 @state == :idle 211 end
# File lib/httpx/connection.rb 174 def create_idle(options = {}) 175 self.class.new(@origin, @options.merge(options)) 176 end
# File lib/httpx/connection.rb 344 def deactivate 345 transition(:inactive) 346 end
# File lib/httpx/connection.rb 378 def disconnect 379 return unless @current_session && @current_selector 380 381 emit(:close) 382 @current_session = @current_selector = nil 383 end
bypasses the state machine to force closing of connections still connecting. only used for Happy Eyeballs v2.
# File lib/httpx/connection.rb 286 def force_reset(cloned = false) 287 @state = :closing 288 @cloned = cloned 289 transition(:closed) 290 end
# File lib/httpx/connection.rb 370 def handle_connect_error(error) 371 return handle_error(error) unless @sibling && @sibling.connecting? 372 373 @sibling.merge(self) 374 375 force_reset(true) 376 end
# File lib/httpx/connection.rb 352 def handle_socket_timeout(interval) 353 error = OperationTimeoutError.new(interval, "timed out while waiting on select") 354 error.set_backtrace(caller) 355 on_error(error) 356 end
# File lib/httpx/connection.rb 333 def idling 334 purge_after_closed 335 @write_buffer.clear 336 transition(:idle) 337 @parser = nil if @parser 338 end
# File lib/httpx/connection.rb 213 def inflight? 214 @parser && ( 215 # parser may be dealing with other requests (possibly started from a different fiber) 216 !@parser.empty? || 217 # connection may be doing connection termination handshake 218 !@write_buffer.empty? 219 ) 220 end
:nocov:
# File lib/httpx/connection.rb 386 def inspect 387 "#<#{self.class}:#{object_id} " \ 388 "@origin=#{@origin} " \ 389 "@state=#{@state} " \ 390 "@pending=#{@pending.size} " \ 391 "@io=#{@io}>" 392 end
# File lib/httpx/connection.rb 222 def interests 223 # connecting 224 if connecting? 225 connect 226 227 return @io.interests if connecting? 228 end 229 230 return @parser.interests if @parser 231 232 nil 233 rescue StandardError => e 234 emit(:error, e) 235 nil 236 end
# File lib/httpx/connection.rb 203 def io_connected? 204 return @coalesced_connection.io_connected? if @coalesced_connection 205 206 @io && @io.state == :connected 207 end
# File lib/httpx/connection.rb 129 def match?(uri, options) 130 return false if !used? && (@state == :closing || @state == :closed) 131 132 ( 133 @origins.include?(uri.origin) && 134 # if there is more than one origin to match, it means that this connection 135 # was the result of coalescing. To prevent blind trust in the case where the 136 # origin came from an ORIGIN frame, we're going to verify the hostname with the 137 # SSL certificate 138 (@origins.size == 1 || @origin == uri.origin || (@io.is_a?(SSL) && @io.verify_hostname(uri.host))) 139 ) && @options == options 140 end
# File lib/httpx/connection.rb 178 def merge(connection) 179 @origins |= connection.instance_variable_get(:@origins) 180 if @ssl_session.nil? && connection.ssl_session 181 @ssl_session = connection.ssl_session 182 @io.session_new_cb do |sess| 183 @ssl_session = sess 184 end if @io 185 end 186 connection.purge_pending do |req| 187 send(req) 188 end 189 end
# File lib/httpx/connection.rb 142 def mergeable?(connection) 143 return false if @state == :closing || @state == :closed || !@io 144 145 return false unless connection.addresses 146 147 ( 148 (open? && @origin == connection.origin) || 149 !(@io.addresses & (connection.addresses || [])).empty? 150 ) && @options == connection.options 151 end
# File lib/httpx/connection.rb 348 def open? 349 @state == :open || @state == :inactive 350 end
# File lib/httpx/connection.rb 191 def purge_pending(&block) 192 pendings = [] 193 if @parser 194 @inflight -= @parser.pending.size 195 pendings << @parser.pending 196 end 197 pendings << @pending 198 pendings.each do |pending| 199 pending.reject!(&block) 200 end 201 end
# File lib/httpx/connection.rb 292 def reset 293 return if @state == :closing || @state == :closed 294 295 transition(:closing) 296 297 transition(:closed) 298 end
# File lib/httpx/connection.rb 300 def send(request) 301 return @coalesced_connection.send(request) if @coalesced_connection 302 303 if @parser && !@write_buffer.full? 304 if @response_received_at && @keep_alive_timeout && 305 Utils.elapsed_time(@response_received_at) > @keep_alive_timeout 306 # when pushing a request into an existing connection, we have to check whether there 307 # is the possibility that the connection might have extended the keep alive timeout. 308 # for such cases, we want to ping for availability before deciding to shovel requests. 309 log(level: 3) { "keep alive timeout expired, pinging connection..." } 310 @pending << request 311 transition(:active) if @state == :inactive 312 parser.ping 313 request.ping! 314 return 315 end 316 317 send_request_to_parser(request) 318 else 319 @pending << request 320 end 321 end
# File lib/httpx/connection.rb 358 def sibling=(connection) 359 @sibling = connection 360 361 return unless connection 362 363 @main_sibling = connection.sibling.nil? 364 365 return unless @main_sibling 366 367 connection.sibling = self 368 end
# File lib/httpx/connection.rb 272 def terminate 273 case @state 274 when :idle 275 purge_after_closed 276 emit(:terminate) 277 when :closed 278 @connected_at = nil 279 end 280 281 close 282 end
# File lib/httpx/connection.rb 323 def timeout 324 return if @state == :closed || @state == :inactive 325 326 return @timeout if @timeout 327 328 return @options.timeout[:connect_timeout] if @state == :idle 329 330 @options.timeout[:operation_timeout] 331 end