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
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
close()
[show source]
    # File lib/httpx/connection.rb
238 def close
239   transition(:active) if @state == :inactive
240 
241   @parser.close if @parser
242 end
coalescable?(connection)

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

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

coalesces self into connection.

[show source]
    # File lib/httpx/connection.rb
124 def coalesce!(connection)
125   @coalesced_connection = connection
126 
127   close_sibling
128   connection.merge(self)
129 end
coalesced?()
[show source]
    # File lib/httpx/connection.rb
131 def coalesced?
132   @coalesced_connection
133 end
connecting?()
[show source]
    # File lib/httpx/connection.rb
179 def connecting?
180   @state == :idle
181 end
deactivate()
[show source]
    # File lib/httpx/connection.rb
333 def deactivate
334   transition(:inactive)
335 end
disconnect()

disconnects from the current session it’s attached to

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

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

[show source]
    # 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
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
275 def force_reset(cloned = false)
276   @state = :closing
277   @cloned = cloned
278   transition(:closed)
279 end
handle_connect_error(error)
[show source]
    # 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
handle_socket_timeout(interval)
[show source]
    # 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
idling()
[show source]
    # 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
inflight?()
[show source]
    # 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
inspect()

:nocov:

[show source]
    # 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
interests()
[show source]
    # 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
io_connected?()
[show source]
    # 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
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   @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
merge(connection)
[show source]
    # 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
mergeable?(connection)
[show source]
    # 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
on_error(error, request = nil)
[show source]
    # 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
open?()
[show source]
    # File lib/httpx/connection.rb
337 def open?
338   @state == :open || @state == :inactive
339 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
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
reset()
[show source]
    # 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
send(request)
[show source]
    # 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
sibling=(connection)
[show source]
    # 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
terminate()
[show source]
    # 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
timeout()
[show source]
    # 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
to_io()
[show source]
    # File lib/httpx/connection.rb
208 def to_io
209   @io.to_io
210 end
used?()
[show source]
    # File lib/httpx/connection.rb
329 def used?
330   @connected_at
331 end