class HTTPX::Connection

  1. lib/httpx/connection.rb
  2. lib/httpx/connection/http1.rb
  3. lib/httpx/connection/http2.rb
  4. lib/httpx/plugins/h2c.rb
  5. show all
Superclass: Object

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.

Included modules

  1. Loggable
  2. Callbacks

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

new(uri, options)
[show source]
   # 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

addresses()
[show source]
   # File lib/httpx/connection.rb
92 def addresses
93   @io && @io.addresses
94 end
addresses=(addrs)

this is a semi-private method, to be used by the resolver to initiate the io object.

[show source]
   # 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
addresses?()
[show source]
   # File lib/httpx/connection.rb
96 def addresses?
97   @io && @io.addresses?
98 end
call()
[show source]
    # 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
close()
[show source]
    # File lib/httpx/connection.rb
243 def close
244   transition(:active) if @state == :inactive
245 
246   @parser.close if @parser
247 end
coalescable?(connection)

coalescable connections need to be mergeable! but internally, mergeable? is called before coalescable?

[show source]
    # 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
coalesce!(connection)

coalesces self into connection.

[show source]
    # File lib/httpx/connection.rb
125 def coalesce!(connection)
126   @coalesced_connection = connection
127 
128   close_sibling
129   connection.merge(self)
130 end
coalesced?()
[show source]
    # File lib/httpx/connection.rb
132 def coalesced?
133   @coalesced_connection
134 end
connecting?()
[show source]
    # File lib/httpx/connection.rb
184 def connecting?
185   @state == :idle
186 end
create_idle(options = {})
[show source]
    # File lib/httpx/connection.rb
149 def create_idle(options = {})
150   self.class.new(@origin, @options.merge(options))
151 end
deactivate()
[show source]
    # File lib/httpx/connection.rb
338 def deactivate
339   transition(:inactive)
340 end
disconnect()

disconnects from the current session it’s attached to

[show source]
    # 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
force_close(delete_pending = false)

bypasses state machine rules while setting the connection in the :closed state.

[show source]
    # 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
force_reset(cloned = false)

bypasses the state machine to force closing of connections still connecting. only used for Happy Eyeballs v2.

[show source]
    # File lib/httpx/connection.rb
280 def force_reset(cloned = false)
281   @state = :closing
282   @cloned = cloned
283   transition(:closed)
284 end
handle_connect_error(error)
[show source]
    # 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
handle_socket_timeout(interval)
[show source]
    # 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
idling()
[show source]
    # 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
inflight?()
[show source]
    # 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
inspect()

:nocov:

[show source]
    # 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
interests()
[show source]
    # 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
io_connected?()
[show source]
    # 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
match?(uri, options)
[show source]
    # 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
merge(connection)
[show source]
    # 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
mergeable?(connection)
[show source]
    # 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
on_error(error, request = nil)
[show source]
    # 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
open?()
[show source]
    # File lib/httpx/connection.rb
342 def open?
343   @state == :open || @state == :inactive
344 end
peer()
[show source]
   # File lib/httpx/connection.rb
78 def peer
79   @origin
80 end
purge_pending(&block)
[show source]
    # 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
reset()
[show source]
    # 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
send(request)
[show source]
    # 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
sibling=(connection)
[show source]
    # 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
terminate()
[show source]
    # 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
timeout()
[show source]
    # 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
to_io()
[show source]
    # File lib/httpx/connection.rb
213 def to_io
214   @io.to_io
215 end
used?()
[show source]
    # File lib/httpx/connection.rb
334 def used?
335   @connected_at
336 end