Class: HTTPX::Connection

Inherits:
Object
  • Object
show all
Extended by:
Forwardable
Includes:
Callbacks, Loggable, Registry
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 collapse

BUFFER_SIZE =
1 << 14

Constants included from Loggable

Loggable::COLORS

Constants included from Registry

Registry::Error

Instance Attribute Summary collapse

Instance Method Summary collapse

Methods included from Callbacks

#emit, #on, #once

Methods included from Loggable

#log, #log_exception

Methods included from Registry

extended, included

Constructor Details

#initialize(type, uri, options) ⇒ Connection



51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
# File 'lib/httpx/connection.rb', line 51

def initialize(type, uri, options)
  @type = type
  @origins = [uri.origin]
  @origin = URI(uri.origin)
  @options = Options.new(options)
  @window_size = @options.window_size
  @read_buffer = Buffer.new(BUFFER_SIZE)
  @write_buffer = Buffer.new(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 = IO.registry(@type).new(@origin, nil, @options)
    parser
  else
    transition(:idle)
  end
end

Instance Attribute Details

#optionsObject (readonly)

Returns the value of attribute options.



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

def options
  @options
end

#originObject (readonly)

Returns the value of attribute origin.



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

def origin
  @origin
end

#pendingObject (readonly)

Returns the value of attribute pending.



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

def pending
  @pending
end

#stateObject (readonly)

Returns the value of attribute state.



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

def state
  @state
end

#timeoutObject (readonly)

Returns the value of attribute timeout.



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

def timeout
  @timeout
end

Instance Method Details

#addressesObject



78
79
80
# File 'lib/httpx/connection.rb', line 78

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.



74
75
76
# File 'lib/httpx/connection.rb', line 74

def addresses=(addrs)
  @io ||= IO.registry(@type).new(@origin, addrs, @options) # rubocop:disable Naming/MemoizedInstanceVariableName
end

#callObject



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

def call
  @timeout = @timeout_threshold
  case @state
  when :closed
    return
  when :closing
    dwrite
    transition(:closed)
    emit(:close)
  when :open
    consume
  end
  nil
end

#closeObject



168
169
170
171
# File 'lib/httpx/connection.rb', line 168

def close
  @parser.close if @parser
  transition(:closing)
end

#coalescable?(connection) ⇒ Boolean

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



96
97
98
99
100
101
102
# File 'lib/httpx/connection.rb', line 96

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

#connecting?Boolean



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

def connecting?
  @state == :idle
end

#inflight?Boolean



144
145
146
# File 'lib/httpx/connection.rb', line 144

def inflight?
  @parser && !@parser.empty? && !@write_buffer.empty?
end

#interestsObject



148
149
150
151
152
153
154
155
156
157
158
# File 'lib/httpx/connection.rb', line 148

def interests
  return :w if @state == :idle

  readable = !@read_buffer.full?
  writable = !@write_buffer.empty?
  if readable
    writable ? :rw : :r
  else
    writable ? :w : :r
  end
end

#match?(uri, options) ⇒ Boolean



82
83
84
85
86
# File 'lib/httpx/connection.rb', line 82

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

  (@origins.include?(uri.origin) || match_altsvcs?(uri)) && @options == options
end

#match_altsvcs?(uri) ⇒ Boolean

checks if this is connection is an alternative service of uri



133
134
135
136
137
138
# File 'lib/httpx/connection.rb', line 133

def match_altsvcs?(uri)
  AltSvc.cached_altsvc(@origin).any? do |altsvc|
    origin = altsvc["origin"]
    origin.altsvc_match?(uri.origin)
  end
end

#merge(connection) ⇒ Object



104
105
106
107
108
109
110
# File 'lib/httpx/connection.rb', line 104

def merge(connection)
  @origins += connection.instance_variable_get(:@origins)
  pending = connection.instance_variable_get(:@pending)
  pending.each do |req, args|
    send(req, args)
  end
end

#mergeable?(connection) ⇒ Boolean



88
89
90
91
92
# File 'lib/httpx/connection.rb', line 88

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

  !(@io.addresses & connection.addresses).empty? && @options == connection.options
end

#purge_pendingObject



123
124
125
126
127
128
129
# File 'lib/httpx/connection.rb', line 123

def purge_pending
  [@parser.pending, @pending].each do |pending|
    pending.reject! do |request, *args|
      yield(request, args)
    end
  end
end

#resetObject



173
174
175
176
177
# File 'lib/httpx/connection.rb', line 173

def reset
  transition(:closing)
  transition(:closed)
  emit(:close)
end

#send(request) ⇒ Object



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

def send(request)
  if @error_response
    emit(:response, request, @error_response)
  elsif @parser && !@write_buffer.full?
    request.headers["alt-used"] = @origin.authority if match_altsvcs?(request.uri)
    parser.send(request)
  else
    @pending << request
  end
end

#to_ioObject



160
161
162
163
164
165
166
# File 'lib/httpx/connection.rb', line 160

def to_io
  case @state
  when :idle
    transition(:open)
  end
  @io.to_io
end

#unmerge(connection) ⇒ Object



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

def unmerge(connection)
  @origins -= connection.instance_variable_get(:@origins)
  purge_pending do |request|
    request.uri.origin == connection.origin && begin
      request.transition(:idle)
      connection.send(request)
      true
    end
  end
end