Class: HTTPX::Connection

Inherits:
Object
  • Object
show all
Extended by:
Forwardable
Includes:
Callbacks, Loggable
Defined in:
lib/httpx/connection.rb

Overview

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.

Defined Under Namespace

Classes: HTTP1, HTTP2

Constant Summary

Constants included from Loggable

Loggable::COLORS, Loggable::USE_DEBUG_LOG

Instance Attribute Summary collapse

Instance Method Summary collapse

Methods included from Callbacks

#callbacks_for?, #emit, #on, #once

Methods included from Loggable

#log, #log_exception, #log_redact

Constructor Details

#initialize(uri, options) ⇒ Connection

Returns a new instance of Connection.



52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
# File 'lib/httpx/connection.rb', line 52

def initialize(uri, options)
  @current_session = @current_selector = @sibling = @coalesced_connection = nil
  @exhausted = @cloned = @main_sibling = false

  @options = Options.new(options)
  @type = initialize_type(uri, @options)
  @origins = [uri.origin]
  @origin = Utils.to_uri(uri.origin)
  @window_size = @options.window_size
  @read_buffer = Buffer.new(@options.buffer_size)
  @write_buffer = Buffer.new(@options.buffer_size)
  @pending = []

  on(:error, &method(:on_error))
  if @options.io
    # if there's an already open IO, get its
    # peer address, and force-initiate the parser
    transition(:already_open)
    @io = build_socket
    parser
  else
    transition(:idle)
  end
  on(:close) do
    next if @exhausted # it'll reset

    # may be called after ":close" above, so after the connection has been checked back in.
    # next unless @current_session

    next unless @current_session

    @current_session.deselect_connection(self, @current_selector, @cloned)
  end
  on(:terminate) do
    next if @exhausted # it'll reset

    current_session = @current_session
    current_selector = @current_selector

    # may be called after ":close" above, so after the connection has been checked back in.
    next unless current_session && current_selector

    current_session.deselect_connection(self, current_selector)
  end

  on(:altsvc) do |alt_origin, origin, alt_params|
    build_altsvc_connection(alt_origin, origin, alt_params)
  end

  @inflight = 0
  @keep_alive_timeout = @options.timeout[:keep_alive_timeout]

  self.addresses = @options.addresses if @options.addresses
end

Instance Attribute Details

#current_selector=(value) ⇒ Object (writeonly)

Sets the attribute current_selector

Parameters:

  • value

    the value to set the attribute current_selector to.



46
47
48
# File 'lib/httpx/connection.rb', line 46

def current_selector=(value)
  @current_selector = value
end

#current_sessionObject

Returns the value of attribute current_session.



48
49
50
# File 'lib/httpx/connection.rb', line 48

def current_session
  @current_session
end

#familyObject

Returns the value of attribute family.



48
49
50
# File 'lib/httpx/connection.rb', line 48

def family
  @family
end

#ioObject (readonly)

Returns the value of attribute io.



44
45
46
# File 'lib/httpx/connection.rb', line 44

def io
  @io
end

#optionsObject (readonly)

Returns the value of attribute options.



44
45
46
# File 'lib/httpx/connection.rb', line 44

def options
  @options
end

#originObject (readonly)

Returns the value of attribute origin.



44
45
46
# File 'lib/httpx/connection.rb', line 44

def origin
  @origin
end

#originsObject (readonly)

Returns the value of attribute origins.



44
45
46
# File 'lib/httpx/connection.rb', line 44

def origins
  @origins
end

#pendingObject (readonly)

Returns the value of attribute pending.



44
45
46
# File 'lib/httpx/connection.rb', line 44

def pending
  @pending
end

#sibling=(connection) ⇒ Object



359
360
361
362
363
364
365
366
367
368
369
# File 'lib/httpx/connection.rb', line 359

def sibling=(connection)
  @sibling = connection

  return unless connection

  @main_sibling = connection.sibling.nil?

  return unless @main_sibling

  connection.sibling = self
end

#ssl_sessionObject (readonly)

Returns the value of attribute ssl_session.



44
45
46
# File 'lib/httpx/connection.rb', line 44

def ssl_session
  @ssl_session
end

#stateObject (readonly)

Returns the value of attribute state.



44
45
46
# File 'lib/httpx/connection.rb', line 44

def state
  @state
end

#typeObject (readonly)

Returns the value of attribute type.



44
45
46
# File 'lib/httpx/connection.rb', line 44

def type
  @type
end

Instance Method Details

#addressesObject



121
122
123
# File 'lib/httpx/connection.rb', line 121

def addresses
  @io && @io.addresses
end

#addresses=(addrs) ⇒ Object

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



113
114
115
116
117
118
119
# File 'lib/httpx/connection.rb', line 113

def addresses=(addrs)
  if @io
    @io.add_addresses(addrs)
  else
    @io = build_socket(addrs)
  end
end

#callObject



247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
# File 'lib/httpx/connection.rb', line 247

def call
  case @state
  when :idle
    connect
    consume
  when :closed
    return
  when :closing
    consume
    transition(:closed)
  when :open
    consume
  end
  nil
rescue StandardError => e
  @write_buffer.clear
  emit(:error, e)
  raise e
end

#closeObject



267
268
269
270
271
# File 'lib/httpx/connection.rb', line 267

def close
  transition(:active) if @state == :inactive

  @parser.close if @parser
end

#coalescable?(connection) ⇒ Boolean

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

Returns:

  • (Boolean)


165
166
167
168
169
170
171
172
173
174
# File 'lib/httpx/connection.rb', line 165

def coalescable?(connection)
  if @io.protocol == "h2" &&
     @origin.scheme == "https" &&
     connection.origin.scheme == "https" &&
     @io.can_verify_peer?
    @io.verify_hostname(connection.origin.host)
  else
    @origin == connection.origin
  end
end

#coalesce!(connection) ⇒ Object

coalesces self into connection.



156
157
158
159
160
161
# File 'lib/httpx/connection.rb', line 156

def coalesce!(connection)
  @coalesced_connection = connection

  close_sibling
  connection.merge(self)
end

#connecting?Boolean

Returns:

  • (Boolean)


211
212
213
# File 'lib/httpx/connection.rb', line 211

def connecting?
  @state == :idle
end

#create_idle(options = {}) ⇒ Object



176
177
178
# File 'lib/httpx/connection.rb', line 176

def create_idle(options = {})
  self.class.new(@origin, @options.merge(options))
end

#deactivateObject



345
346
347
# File 'lib/httpx/connection.rb', line 345

def deactivate
  transition(:inactive)
end

#disconnectObject



381
382
383
384
385
386
387
# File 'lib/httpx/connection.rb', line 381

def disconnect
  return unless @current_session && @current_selector

  emit(:close)
  @current_session = nil
  @current_selector = nil
end

#expired?Boolean

Returns:

  • (Boolean)


138
139
140
141
142
# File 'lib/httpx/connection.rb', line 138

def expired?
  return false unless @io

  @io.expired?
end

#force_reset(cloned = false) ⇒ Object

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



287
288
289
290
291
# File 'lib/httpx/connection.rb', line 287

def force_reset(cloned = false)
  @state = :closing
  @cloned = cloned
  transition(:closed)
end

#handle_connect_error(error) ⇒ Object



371
372
373
374
375
376
377
378
379
# File 'lib/httpx/connection.rb', line 371

def handle_connect_error(error)
  @connect_error = error

  return handle_error(error) unless @sibling && @sibling.connecting?

  @sibling.merge(self)

  force_reset(true)
end

#handle_socket_timeout(interval) ⇒ Object



353
354
355
356
357
# File 'lib/httpx/connection.rb', line 353

def handle_socket_timeout(interval)
  error = OperationTimeoutError.new(interval, "timed out while waiting on select")
  error.set_backtrace(caller)
  on_error(error)
end

#idlingObject



334
335
336
337
338
339
# File 'lib/httpx/connection.rb', line 334

def idling
  purge_after_closed
  @write_buffer.clear
  transition(:idle)
  @parser = nil if @parser
end

#inflight?Boolean

Returns:

  • (Boolean)


215
216
217
218
219
220
221
222
# File 'lib/httpx/connection.rb', line 215

def inflight?
  @parser && (
    # parser may be dealing with other requests (possibly started from a different fiber)
    !@parser.empty? ||
    # connection may be doing connection termination handshake
    !@write_buffer.empty?
  )
end

#inspectObject

:nocov:



390
391
392
393
394
395
396
# File 'lib/httpx/connection.rb', line 390

def inspect
  "#<#{self.class}:#{object_id} " \
    "@origin=#{@origin} " \
    "@state=#{@state} " \
    "@pending=#{@pending.size} " \
    "@io=#{@io}>"
end

#interestsObject



224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
# File 'lib/httpx/connection.rb', line 224

def interests
  # connecting
  if connecting?
    connect

    return @io.interests if connecting?
  end

  # if the write buffer is full, we drain it
  return :w unless @write_buffer.empty?

  return @parser.interests if @parser

  nil
rescue StandardError => e
  emit(:error, e)
  nil
end

#io_connected?Boolean

Returns:

  • (Boolean)


205
206
207
208
209
# File 'lib/httpx/connection.rb', line 205

def io_connected?
  return @coalesced_connection.io_connected? if @coalesced_connection

  @io && @io.state == :connected
end

#match?(uri, options) ⇒ Boolean

Returns:

  • (Boolean)


125
126
127
128
129
130
131
132
133
134
135
136
# File 'lib/httpx/connection.rb', line 125

def match?(uri, options)
  return false if !used? && (@state == :closing || @state == :closed)

  (
    @origins.include?(uri.origin) &&
    # if there is more than one origin to match, it means that this connection
    # was the result of coalescing. To prevent blind trust in the case where the
    # origin came from an ORIGIN frame, we're going to verify the hostname with the
    # SSL certificate
    (@origins.size == 1 || @origin == uri.origin || (@io.is_a?(SSL) && @io.verify_hostname(uri.host)))
  ) && @options == options
end

#merge(connection) ⇒ Object



180
181
182
183
184
185
186
187
188
189
190
191
# File 'lib/httpx/connection.rb', line 180

def merge(connection)
  @origins |= connection.instance_variable_get(:@origins)
  if connection.ssl_session
    @ssl_session = connection.ssl_session
    @io.session_new_cb do |sess|
      @ssl_session = sess
    end if @io
  end
  connection.purge_pending do |req|
    send(req)
  end
end

#mergeable?(connection) ⇒ Boolean

Returns:

  • (Boolean)


144
145
146
147
148
149
150
151
152
153
# File 'lib/httpx/connection.rb', line 144

def mergeable?(connection)
  return false if @state == :closing || @state == :closed || !@io

  return false unless connection.addresses

  (
    (open? && @origin == connection.origin) ||
    !(@io.addresses & (connection.addresses || [])).empty?
  ) && @options == connection.options
end

#open?Boolean

Returns:

  • (Boolean)


349
350
351
# File 'lib/httpx/connection.rb', line 349

def open?
  @state == :open || @state == :inactive
end

#peerObject



107
108
109
# File 'lib/httpx/connection.rb', line 107

def peer
  @origin
end

#purge_pending(&block) ⇒ Object



193
194
195
196
197
198
199
200
201
202
203
# File 'lib/httpx/connection.rb', line 193

def purge_pending(&block)
  pendings = []
  if @parser
    @inflight -= @parser.pending.size
    pendings << @parser.pending
  end
  pendings << @pending
  pendings.each do |pending|
    pending.reject!(&block)
  end
end

#resetObject



293
294
295
296
297
298
299
# File 'lib/httpx/connection.rb', line 293

def reset
  return if @state == :closing || @state == :closed

  transition(:closing)

  transition(:closed)
end

#send(request) ⇒ Object



301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
# File 'lib/httpx/connection.rb', line 301

def send(request)
  return @coalesced_connection.send(request) if @coalesced_connection

  if @parser && !@write_buffer.full?
    if @response_received_at && @keep_alive_timeout &&
       Utils.elapsed_time(@response_received_at) > @keep_alive_timeout
      # when pushing a request into an existing connection, we have to check whether there
      # is the possibility that the connection might have extended the keep alive timeout.
      # for such cases, we want to ping for availability before deciding to shovel requests.
      log(level: 3) { "keep alive timeout expired, pinging connection..." }
      @pending << request
      transition(:active) if @state == :inactive
      parser.ping
      request.ping!
      return
    end

    send_request_to_parser(request)
  else
    @pending << request
  end
end

#terminateObject



273
274
275
276
277
278
279
280
281
282
283
# File 'lib/httpx/connection.rb', line 273

def terminate
  case @state
  when :idle
    purge_after_closed
    emit(:terminate)
  when :closed
    @connected_at = nil
  end

  close
end

#timeoutObject



324
325
326
327
328
329
330
331
332
# File 'lib/httpx/connection.rb', line 324

def timeout
  return if @state == :closed || @state == :inactive

  return @timeout if @timeout

  return @options.timeout[:connect_timeout] if @state == :idle

  @options.timeout[:operation_timeout]
end

#to_ioObject



243
244
245
# File 'lib/httpx/connection.rb', line 243

def to_io
  @io.to_io
end

#used?Boolean

Returns:

  • (Boolean)


341
342
343
# File 'lib/httpx/connection.rb', line 341

def used?
  @connected_at
end