httpx 1.3.4 → 1.4.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/doc/release_notes/1_4_0.md +43 -0
- data/lib/httpx/adapters/faraday.rb +2 -0
- data/lib/httpx/adapters/webmock.rb +11 -5
- data/lib/httpx/callbacks.rb +0 -5
- data/lib/httpx/chainable.rb +3 -1
- data/lib/httpx/connection/http2.rb +11 -7
- data/lib/httpx/connection.rb +128 -16
- data/lib/httpx/errors.rb +12 -0
- data/lib/httpx/loggable.rb +5 -5
- data/lib/httpx/options.rb +26 -16
- data/lib/httpx/plugins/aws_sigv4.rb +31 -16
- data/lib/httpx/plugins/callbacks.rb +12 -2
- data/lib/httpx/plugins/circuit_breaker.rb +0 -5
- data/lib/httpx/plugins/content_digest.rb +202 -0
- data/lib/httpx/plugins/expect.rb +4 -3
- data/lib/httpx/plugins/follow_redirects.rb +7 -8
- data/lib/httpx/plugins/h2c.rb +23 -20
- data/lib/httpx/plugins/internal_telemetry.rb +27 -0
- data/lib/httpx/plugins/persistent.rb +16 -0
- data/lib/httpx/plugins/proxy/http.rb +17 -19
- data/lib/httpx/plugins/proxy.rb +91 -93
- data/lib/httpx/plugins/retries.rb +5 -8
- data/lib/httpx/plugins/upgrade.rb +5 -10
- data/lib/httpx/plugins/webdav.rb +6 -0
- data/lib/httpx/plugins/xml.rb +76 -0
- data/lib/httpx/pool.rb +73 -244
- data/lib/httpx/request/body.rb +16 -12
- data/lib/httpx/request.rb +1 -1
- data/lib/httpx/resolver/https.rb +12 -19
- data/lib/httpx/resolver/multi.rb +34 -16
- data/lib/httpx/resolver/native.rb +36 -13
- data/lib/httpx/resolver/resolver.rb +49 -11
- data/lib/httpx/resolver/system.rb +29 -11
- data/lib/httpx/resolver.rb +21 -14
- data/lib/httpx/response.rb +5 -3
- data/lib/httpx/selector.rb +164 -95
- data/lib/httpx/session.rb +296 -139
- data/lib/httpx/transcoder/gzip.rb +0 -3
- data/lib/httpx/transcoder/json.rb +14 -2
- data/lib/httpx/transcoder/utils/deflater.rb +7 -4
- data/lib/httpx/transcoder/utils/inflater.rb +2 -0
- data/lib/httpx/transcoder.rb +0 -1
- data/lib/httpx/version.rb +1 -1
- data/lib/httpx.rb +19 -20
- data/sig/callbacks.rbs +0 -1
- data/sig/chainable.rbs +4 -0
- data/sig/connection/http2.rbs +1 -1
- data/sig/connection.rbs +14 -3
- data/sig/errors.rbs +6 -0
- data/sig/loggable.rbs +2 -0
- data/sig/options.rbs +7 -0
- data/sig/plugins/aws_sigv4.rbs +8 -2
- data/sig/plugins/content_digest.rbs +51 -0
- data/sig/plugins/cookies/cookie.rbs +9 -0
- data/sig/plugins/grpc/call.rbs +4 -0
- data/sig/plugins/persistent.rbs +4 -1
- data/sig/plugins/proxy/socks5.rbs +11 -3
- data/sig/plugins/proxy.rbs +18 -11
- data/sig/plugins/push_promise.rbs +3 -0
- data/sig/plugins/rate_limiter.rbs +2 -0
- data/sig/plugins/retries.rbs +1 -1
- data/sig/plugins/ssrf_filter.rbs +26 -0
- data/sig/plugins/webdav.rbs +23 -0
- data/sig/plugins/xml.rbs +37 -0
- data/sig/pool.rbs +25 -33
- data/sig/request/body.rbs +5 -1
- data/sig/resolver/multi.rbs +26 -1
- data/sig/resolver/native.rbs +0 -2
- data/sig/resolver/resolver.rbs +21 -2
- data/sig/resolver.rbs +5 -1
- data/sig/response/buffer.rbs +1 -1
- data/sig/selector.rbs +30 -4
- data/sig/session.rbs +45 -18
- data/sig/transcoder/body.rbs +1 -1
- data/sig/transcoder/chunker.rbs +1 -1
- data/sig/transcoder/deflate.rbs +1 -0
- data/sig/transcoder/form.rbs +8 -0
- data/sig/transcoder/gzip.rbs +4 -1
- data/sig/transcoder/utils/body_reader.rbs +2 -2
- data/sig/transcoder/utils/deflater.rbs +2 -2
- metadata +10 -4
- data/lib/httpx/transcoder/xml.rb +0 -52
- data/sig/transcoder/xml.rbs +0 -22
data/lib/httpx/pool.rb
CHANGED
@@ -1,6 +1,5 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
-
require "forwardable"
|
4
3
|
require "httpx/selector"
|
5
4
|
require "httpx/connection"
|
6
5
|
require "httpx/resolver"
|
@@ -8,110 +7,31 @@ require "httpx/resolver"
|
|
8
7
|
module HTTPX
|
9
8
|
class Pool
|
10
9
|
using ArrayExtensions::FilterMap
|
11
|
-
|
10
|
+
using URIExtensions
|
12
11
|
|
13
|
-
|
12
|
+
POOL_TIMEOUT = 5
|
14
13
|
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
14
|
+
# Sets up the connection pool with the given +options+, which can be the following:
|
15
|
+
#
|
16
|
+
# :max_connections_per_origin :: the maximum number of connections held in the pool pointing to a given origin.
|
17
|
+
# :pool_timeout :: the number of seconds to wait for a connection to a given origin (before raising HTTPX::PoolTimeoutError)
|
18
|
+
#
|
19
|
+
def initialize(options)
|
20
|
+
@max_connections_per_origin = options.fetch(:max_connections_per_origin, Float::INFINITY)
|
21
|
+
@pool_timeout = options.fetch(:pool_timeout, POOL_TIMEOUT)
|
22
|
+
@resolvers = Hash.new { |hs, resolver_type| hs[resolver_type] = [] }
|
23
|
+
@resolver_mtx = Thread::Mutex.new
|
24
24
|
@connections = []
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
ensure
|
29
|
-
@connections.unshift(*connections)
|
30
|
-
end
|
31
|
-
end
|
32
|
-
|
33
|
-
def empty?
|
34
|
-
@connections.empty?
|
35
|
-
end
|
36
|
-
|
37
|
-
def next_tick
|
38
|
-
catch(:jump_tick) do
|
39
|
-
timeout = next_timeout
|
40
|
-
if timeout && timeout.negative?
|
41
|
-
@timers.fire
|
42
|
-
throw(:jump_tick)
|
43
|
-
end
|
44
|
-
|
45
|
-
begin
|
46
|
-
@selector.select(timeout, &:call)
|
47
|
-
@timers.fire
|
48
|
-
rescue TimeoutError => e
|
49
|
-
@timers.fire(e)
|
50
|
-
end
|
51
|
-
end
|
52
|
-
rescue StandardError => e
|
53
|
-
@connections.each do |connection|
|
54
|
-
connection.emit(:error, e)
|
55
|
-
end
|
56
|
-
rescue Exception # rubocop:disable Lint/RescueException
|
57
|
-
@connections.each(&:force_reset)
|
58
|
-
raise
|
59
|
-
end
|
60
|
-
|
61
|
-
def close(connections = @connections)
|
62
|
-
return if connections.empty?
|
63
|
-
|
64
|
-
connections = connections.reject(&:inflight?)
|
65
|
-
connections.each(&:terminate)
|
66
|
-
next_tick until connections.none? { |c| c.state != :idle && @connections.include?(c) }
|
67
|
-
|
68
|
-
# close resolvers
|
69
|
-
outstanding_connections = @connections
|
70
|
-
resolver_connections = @resolvers.each_value.flat_map(&:connections).compact
|
71
|
-
outstanding_connections -= resolver_connections
|
72
|
-
|
73
|
-
return unless outstanding_connections.empty?
|
74
|
-
|
75
|
-
@resolvers.each_value do |resolver|
|
76
|
-
resolver.close unless resolver.closed?
|
77
|
-
end
|
78
|
-
# for https resolver
|
79
|
-
resolver_connections.each(&:terminate)
|
80
|
-
next_tick until resolver_connections.none? { |c| c.state != :idle && @connections.include?(c) }
|
81
|
-
end
|
82
|
-
|
83
|
-
def init_connection(connection, _options)
|
84
|
-
connection.timers = @timers
|
85
|
-
connection.on(:activate) do
|
86
|
-
select_connection(connection)
|
87
|
-
end
|
88
|
-
connection.on(:exhausted) do
|
89
|
-
case connection.state
|
90
|
-
when :closed
|
91
|
-
connection.idling
|
92
|
-
@connections << connection
|
93
|
-
select_connection(connection)
|
94
|
-
when :closing
|
95
|
-
connection.once(:close) do
|
96
|
-
connection.idling
|
97
|
-
@connections << connection
|
98
|
-
select_connection(connection)
|
99
|
-
end
|
100
|
-
end
|
101
|
-
end
|
102
|
-
connection.on(:close) do
|
103
|
-
unregister_connection(connection)
|
104
|
-
end
|
105
|
-
connection.on(:terminate) do
|
106
|
-
unregister_connection(connection, true)
|
107
|
-
end
|
108
|
-
resolve_connection(connection) unless connection.family
|
25
|
+
@connection_mtx = Thread::Mutex.new
|
26
|
+
@origin_counters = Hash.new(0)
|
27
|
+
@origin_conds = Hash.new { |hs, orig| hs[orig] = ConditionVariable.new }
|
109
28
|
end
|
110
29
|
|
111
|
-
def
|
112
|
-
|
113
|
-
|
114
|
-
|
30
|
+
def pop_connection
|
31
|
+
@connection_mtx.synchronize do
|
32
|
+
conn = @connections.shift
|
33
|
+
@origin_conds.delete(conn.origin) if conn && (@origin_counters[conn.origin.to_s] -= 1).zero?
|
34
|
+
conn
|
115
35
|
end
|
116
36
|
end
|
117
37
|
|
@@ -119,185 +39,94 @@ module HTTPX
|
|
119
39
|
# Many hostnames are reachable through the same IP, so we try to
|
120
40
|
# maximize pipelining by opening as few connections as possible.
|
121
41
|
#
|
122
|
-
def
|
123
|
-
|
124
|
-
connection.match?(uri, options)
|
125
|
-
end
|
42
|
+
def checkout_connection(uri, options)
|
43
|
+
return checkout_new_connection(uri, options) if options.io
|
126
44
|
|
127
|
-
|
45
|
+
@connection_mtx.synchronize do
|
46
|
+
acquire_connection(uri, options) || begin
|
47
|
+
if @origin_counters[uri.origin] == @max_connections_per_origin
|
128
48
|
|
129
|
-
|
130
|
-
when :closed
|
131
|
-
conn.idling
|
132
|
-
select_connection(conn)
|
133
|
-
when :closing
|
134
|
-
conn.once(:close) do
|
135
|
-
conn.idling
|
136
|
-
select_connection(conn)
|
137
|
-
end
|
138
|
-
end
|
49
|
+
@origin_conds[uri.origin].wait(@connection_mtx, @pool_timeout)
|
139
50
|
|
140
|
-
|
141
|
-
|
142
|
-
|
143
|
-
private
|
144
|
-
|
145
|
-
def resolve_connection(connection)
|
146
|
-
@connections << connection unless @connections.include?(connection)
|
147
|
-
|
148
|
-
if connection.addresses || connection.open?
|
149
|
-
#
|
150
|
-
# there are two cases in which we want to activate initialization of
|
151
|
-
# connection immediately:
|
152
|
-
#
|
153
|
-
# 1. when the connection already has addresses, i.e. it doesn't need to
|
154
|
-
# resolve a name (not the same as name being an IP, yet)
|
155
|
-
# 2. when the connection is initialized with an external already open IO.
|
156
|
-
#
|
157
|
-
connection.once(:connect_error, &connection.method(:handle_error))
|
158
|
-
on_resolver_connection(connection)
|
159
|
-
return
|
160
|
-
end
|
51
|
+
return acquire_connection(uri, options) || raise(PoolTimeoutError.new(uri.origin, @pool_timeout))
|
52
|
+
end
|
161
53
|
|
162
|
-
|
163
|
-
resolver << try_clone_connection(connection, resolver.family)
|
164
|
-
next if resolver.empty?
|
54
|
+
@origin_counters[uri.origin] += 1
|
165
55
|
|
166
|
-
|
56
|
+
checkout_new_connection(uri, options)
|
57
|
+
end
|
167
58
|
end
|
168
59
|
end
|
169
60
|
|
170
|
-
def
|
171
|
-
connection.
|
172
|
-
|
173
|
-
return connection if connection.family == family
|
174
|
-
|
175
|
-
new_connection = connection.class.new(connection.origin, connection.options)
|
176
|
-
new_connection.family = family
|
61
|
+
def checkin_connection(connection)
|
62
|
+
return if connection.options.io
|
177
63
|
|
178
|
-
|
179
|
-
|
180
|
-
if new_connection.connecting?
|
181
|
-
new_connection.merge(connection)
|
182
|
-
connection.emit(:cloned, new_connection)
|
183
|
-
connection.force_reset
|
184
|
-
else
|
185
|
-
connection.__send__(:handle_error, err)
|
186
|
-
end
|
187
|
-
end
|
64
|
+
@connection_mtx.synchronize do
|
65
|
+
@connections << connection
|
188
66
|
|
189
|
-
|
190
|
-
if new_conn != connection
|
191
|
-
new_conn.merge(connection)
|
192
|
-
connection.force_reset
|
193
|
-
end
|
194
|
-
end
|
195
|
-
new_connection.once(:connect_error) do |err|
|
196
|
-
if connection.connecting?
|
197
|
-
# main connection has the requests
|
198
|
-
connection.merge(new_connection)
|
199
|
-
new_connection.emit(:cloned, connection)
|
200
|
-
new_connection.force_reset
|
201
|
-
else
|
202
|
-
new_connection.__send__(:handle_error, err)
|
203
|
-
end
|
67
|
+
@origin_conds[connection.origin.to_s].signal
|
204
68
|
end
|
205
|
-
|
206
|
-
init_connection(new_connection, connection.options)
|
207
|
-
new_connection
|
208
69
|
end
|
209
70
|
|
210
|
-
def
|
211
|
-
|
212
|
-
found_connection = @connections.find do |ch|
|
213
|
-
ch != connection && ch.mergeable?(connection)
|
214
|
-
end
|
215
|
-
return register_connection(connection) unless found_connection
|
71
|
+
def checkout_mergeable_connection(connection)
|
72
|
+
return if connection.options.io
|
216
73
|
|
217
|
-
|
218
|
-
|
219
|
-
|
220
|
-
else
|
221
|
-
found_connection.once(:open) do
|
222
|
-
coalesce_connections(found_connection, connection)
|
74
|
+
@connection_mtx.synchronize do
|
75
|
+
idx = @connections.find_index do |ch|
|
76
|
+
ch != connection && ch.mergeable?(connection)
|
223
77
|
end
|
78
|
+
@connections.delete_at(idx) if idx
|
224
79
|
end
|
225
80
|
end
|
226
81
|
|
227
|
-
def
|
228
|
-
|
229
|
-
|
230
|
-
connection.emit(:error, error)
|
82
|
+
def reset_resolvers
|
83
|
+
@resolver_mtx.synchronize { @resolvers.clear }
|
231
84
|
end
|
232
85
|
|
233
|
-
def
|
234
|
-
resolver_type =
|
235
|
-
|
86
|
+
def checkout_resolver(options)
|
87
|
+
resolver_type = options.resolver_class
|
88
|
+
resolver_type = Resolver.resolver_for(resolver_type)
|
236
89
|
|
237
|
-
@
|
90
|
+
@resolver_mtx.synchronize do
|
91
|
+
resolvers = @resolvers[resolver_type]
|
238
92
|
|
239
|
-
|
240
|
-
|
93
|
+
idx = resolvers.find_index do |res|
|
94
|
+
res.options == options
|
95
|
+
end
|
96
|
+
resolvers.delete_at(idx) if idx
|
97
|
+
end || checkout_new_resolver(resolver_type, options)
|
241
98
|
end
|
242
99
|
|
243
|
-
def
|
244
|
-
|
245
|
-
|
100
|
+
def checkin_resolver(resolver)
|
101
|
+
@resolver_mtx.synchronize do
|
102
|
+
resolvers = @resolvers[resolver.class]
|
246
103
|
|
247
|
-
|
248
|
-
@connections.delete(connection) if cleanup
|
249
|
-
deselect_connection(connection)
|
250
|
-
end
|
104
|
+
resolver = resolver.multi
|
251
105
|
|
252
|
-
|
253
|
-
|
106
|
+
resolvers << resolver unless resolvers.include?(resolver)
|
107
|
+
end
|
254
108
|
end
|
255
109
|
|
256
|
-
|
257
|
-
@selector.deregister(connection)
|
258
|
-
end
|
110
|
+
private
|
259
111
|
|
260
|
-
def
|
261
|
-
|
112
|
+
def acquire_connection(uri, options)
|
113
|
+
idx = @connections.find_index do |connection|
|
114
|
+
connection.match?(uri, options)
|
115
|
+
end
|
262
116
|
|
263
|
-
|
264
|
-
conn1.merge(conn2)
|
265
|
-
@connections.delete(conn2)
|
117
|
+
@connections.delete_at(idx) if idx
|
266
118
|
end
|
267
119
|
|
268
|
-
def
|
269
|
-
|
270
|
-
@timers.wait_interval,
|
271
|
-
*@resolvers.values.reject(&:closed?).filter_map(&:timeout),
|
272
|
-
*@connections.filter_map(&:timeout),
|
273
|
-
].compact.min
|
120
|
+
def checkout_new_connection(uri, options)
|
121
|
+
options.connection_class.new(uri, options)
|
274
122
|
end
|
275
123
|
|
276
|
-
def
|
277
|
-
|
278
|
-
|
279
|
-
|
280
|
-
|
281
|
-
@resolvers[resolver_type] ||= begin
|
282
|
-
resolver_manager = if resolver_type.multi?
|
283
|
-
Resolver::Multi.new(resolver_type, connection_options)
|
284
|
-
else
|
285
|
-
resolver_type.new(connection_options)
|
286
|
-
end
|
287
|
-
resolver_manager.on(:resolve, &method(:on_resolver_connection))
|
288
|
-
resolver_manager.on(:error, &method(:on_resolver_error))
|
289
|
-
resolver_manager.on(:close, &method(:on_resolver_close))
|
290
|
-
resolver_manager
|
291
|
-
end
|
292
|
-
|
293
|
-
manager = @resolvers[resolver_type]
|
294
|
-
|
295
|
-
(manager.is_a?(Resolver::Multi) && manager.early_resolve(connection)) || manager.resolvers.each do |resolver|
|
296
|
-
resolver.pool = self
|
297
|
-
yield resolver
|
124
|
+
def checkout_new_resolver(resolver_type, options)
|
125
|
+
if resolver_type.multi?
|
126
|
+
Resolver::Multi.new(resolver_type, options)
|
127
|
+
else
|
128
|
+
resolver_type.new(options)
|
298
129
|
end
|
299
|
-
|
300
|
-
manager
|
301
130
|
end
|
302
131
|
end
|
303
132
|
end
|
data/lib/httpx/request/body.rb
CHANGED
@@ -25,20 +25,11 @@ module HTTPX
|
|
25
25
|
# ..., form: { foo: Pathname.open("path/to/file") }) #=> multipart urlencoded encoder
|
26
26
|
# ..., form: { foo: File.open("path/to/file") }) #=> multipart urlencoded encoder
|
27
27
|
# ..., form: { body: "bla") }) #=> raw data encoder
|
28
|
-
def initialize(
|
29
|
-
@headers =
|
28
|
+
def initialize(h, options, **params)
|
29
|
+
@headers = h
|
30
|
+
@body = self.class.initialize_body(params)
|
30
31
|
@options = options.merge(params)
|
31
32
|
|
32
|
-
@body = if body
|
33
|
-
Transcoder::Body.encode(body)
|
34
|
-
elsif form
|
35
|
-
Transcoder::Form.encode(form)
|
36
|
-
elsif json
|
37
|
-
Transcoder::JSON.encode(json)
|
38
|
-
elsif xml
|
39
|
-
Transcoder::Xml.encode(xml)
|
40
|
-
end
|
41
|
-
|
42
33
|
if @body
|
43
34
|
if @options.compress_request_body && @headers.key?("content-encoding")
|
44
35
|
|
@@ -123,6 +114,19 @@ module HTTPX
|
|
123
114
|
# :nocov:
|
124
115
|
|
125
116
|
class << self
|
117
|
+
def initialize_body(params)
|
118
|
+
if (body = params.delete(:body))
|
119
|
+
# @type var body: bodyIO
|
120
|
+
Transcoder::Body.encode(body)
|
121
|
+
elsif (form = params.delete(:form))
|
122
|
+
# @type var form: Transcoder::urlencoded_input
|
123
|
+
Transcoder::Form.encode(form)
|
124
|
+
elsif (json = params.delete(:json))
|
125
|
+
# @type var body: _ToJson
|
126
|
+
Transcoder::JSON.encode(json)
|
127
|
+
end
|
128
|
+
end
|
129
|
+
|
126
130
|
# returns the +body+ wrapped with the correct deflater accordinng to the given +encodisng+.
|
127
131
|
def initialize_deflater_body(body, encoding)
|
128
132
|
case encoding
|
data/lib/httpx/request.rb
CHANGED
@@ -12,7 +12,7 @@ module HTTPX
|
|
12
12
|
using URIExtensions
|
13
13
|
|
14
14
|
# default value used for "user-agent" header, when not overridden.
|
15
|
-
USER_AGENT = "httpx.rb/#{VERSION}"
|
15
|
+
USER_AGENT = "httpx.rb/#{VERSION}".freeze # rubocop:disable Style/RedundantFreeze
|
16
16
|
|
17
17
|
# the upcased string HTTP verb for this request.
|
18
18
|
attr_reader :verb
|
data/lib/httpx/resolver/https.rb
CHANGED
@@ -27,7 +27,7 @@ module HTTPX
|
|
27
27
|
use_get: false,
|
28
28
|
}.freeze
|
29
29
|
|
30
|
-
def_delegators :@resolver_connection, :state, :connecting?, :to_io, :call, :close, :terminate
|
30
|
+
def_delegators :@resolver_connection, :state, :connecting?, :to_io, :call, :close, :terminate, :inflight?
|
31
31
|
|
32
32
|
def initialize(_, options)
|
33
33
|
super
|
@@ -43,7 +43,7 @@ module HTTPX
|
|
43
43
|
end
|
44
44
|
|
45
45
|
def <<(connection)
|
46
|
-
return if @uri.origin == connection.
|
46
|
+
return if @uri.origin == connection.peer.to_s
|
47
47
|
|
48
48
|
@uri_addresses ||= HTTPX::Resolver.nolookup_resolve(@uri.host) || @resolver.getaddresses(@uri.host)
|
49
49
|
|
@@ -66,30 +66,23 @@ module HTTPX
|
|
66
66
|
end
|
67
67
|
|
68
68
|
def resolver_connection
|
69
|
-
|
70
|
-
|
71
|
-
|
72
|
-
|
73
|
-
# only explicity emit addresses if connection didn't pre-resolve, i.e. it's not an IP.
|
74
|
-
catch(:coalesced) do
|
75
|
-
@building_connection = false
|
76
|
-
emit_addresses(connection, @family, @uri_addresses) unless connection.addresses
|
77
|
-
connection
|
78
|
-
end
|
69
|
+
# TODO: leaks connection object into the pool
|
70
|
+
@resolver_connection ||= @current_session.find_connection(@uri, @current_selector,
|
71
|
+
@options.merge(ssl: { alpn_protocols: %w[h2] })).tap do |conn|
|
72
|
+
emit_addresses(conn, @family, @uri_addresses) unless conn.addresses
|
79
73
|
end
|
80
74
|
end
|
81
75
|
|
82
76
|
private
|
83
77
|
|
84
78
|
def resolve(connection = @connections.first, hostname = nil)
|
85
|
-
return if @building_connection
|
86
79
|
return unless connection
|
87
80
|
|
88
81
|
hostname ||= @queries.key(connection)
|
89
82
|
|
90
83
|
if hostname.nil?
|
91
|
-
hostname = connection.
|
92
|
-
log { "resolver: resolve IDN #{connection.
|
84
|
+
hostname = connection.peer.host
|
85
|
+
log { "resolver: resolve IDN #{connection.peer.non_ascii_hostname} as #{hostname}" } if connection.peer.non_ascii_hostname
|
93
86
|
|
94
87
|
hostname = @resolver.generate_candidates(hostname).each do |name|
|
95
88
|
@queries[name.to_s] = connection
|
@@ -108,7 +101,7 @@ module HTTPX
|
|
108
101
|
@connections << connection
|
109
102
|
rescue ResolveError, Resolv::DNS::EncodeError => e
|
110
103
|
reset_hostname(hostname)
|
111
|
-
emit_resolve_error(connection, connection.
|
104
|
+
emit_resolve_error(connection, connection.peer.host, e)
|
112
105
|
end
|
113
106
|
end
|
114
107
|
|
@@ -117,7 +110,7 @@ module HTTPX
|
|
117
110
|
rescue StandardError => e
|
118
111
|
hostname = @requests.delete(request)
|
119
112
|
connection = reset_hostname(hostname)
|
120
|
-
emit_resolve_error(connection, connection.
|
113
|
+
emit_resolve_error(connection, connection.peer.host, e)
|
121
114
|
else
|
122
115
|
# @type var response: HTTPX::Response
|
123
116
|
parse(request, response)
|
@@ -156,7 +149,7 @@ module HTTPX
|
|
156
149
|
when :decode_error
|
157
150
|
host = @requests.delete(request)
|
158
151
|
connection = reset_hostname(host)
|
159
|
-
emit_resolve_error(connection, connection.
|
152
|
+
emit_resolve_error(connection, connection.peer.host, result)
|
160
153
|
end
|
161
154
|
end
|
162
155
|
|
@@ -176,7 +169,7 @@ module HTTPX
|
|
176
169
|
alias_address = answers[address["alias"]]
|
177
170
|
if alias_address.nil?
|
178
171
|
reset_hostname(address["name"])
|
179
|
-
if
|
172
|
+
if early_resolve(connection, hostname: address["alias"])
|
180
173
|
@connections.delete(connection)
|
181
174
|
else
|
182
175
|
resolve(connection, address["alias"])
|
data/lib/httpx/resolver/multi.rb
CHANGED
@@ -8,27 +8,45 @@ module HTTPX
|
|
8
8
|
include Callbacks
|
9
9
|
using ArrayExtensions::FilterMap
|
10
10
|
|
11
|
-
attr_reader :resolvers
|
11
|
+
attr_reader :resolvers, :options
|
12
12
|
|
13
13
|
def initialize(resolver_type, options)
|
14
|
+
@current_selector = nil
|
15
|
+
@current_session = nil
|
14
16
|
@options = options
|
15
17
|
@resolver_options = @options.resolver_options
|
16
18
|
|
17
19
|
@resolvers = options.ip_families.map do |ip_family|
|
18
20
|
resolver = resolver_type.new(ip_family, options)
|
19
|
-
resolver.
|
20
|
-
resolver.on(:error, &method(:on_resolver_error))
|
21
|
-
resolver.on(:close) { on_resolver_close(resolver) }
|
21
|
+
resolver.multi = self
|
22
22
|
resolver
|
23
23
|
end
|
24
24
|
|
25
25
|
@errors = Hash.new { |hs, k| hs[k] = [] }
|
26
26
|
end
|
27
27
|
|
28
|
+
def current_selector=(s)
|
29
|
+
@current_selector = s
|
30
|
+
@resolvers.each { |r| r.__send__(__method__, s) }
|
31
|
+
end
|
32
|
+
|
33
|
+
def current_session=(s)
|
34
|
+
@current_session = s
|
35
|
+
@resolvers.each { |r| r.__send__(__method__, s) }
|
36
|
+
end
|
37
|
+
|
28
38
|
def closed?
|
29
39
|
@resolvers.all?(&:closed?)
|
30
40
|
end
|
31
41
|
|
42
|
+
def empty?
|
43
|
+
@resolvers.all?(&:empty?)
|
44
|
+
end
|
45
|
+
|
46
|
+
def inflight?
|
47
|
+
@resolvers.any(&:inflight?)
|
48
|
+
end
|
49
|
+
|
32
50
|
def timeout
|
33
51
|
@resolvers.filter_map(&:timeout).min
|
34
52
|
end
|
@@ -42,10 +60,11 @@ module HTTPX
|
|
42
60
|
end
|
43
61
|
|
44
62
|
def early_resolve(connection)
|
45
|
-
hostname = connection.
|
63
|
+
hostname = connection.peer.host
|
46
64
|
addresses = @resolver_options[:cache] && (connection.addresses || HTTPX::Resolver.nolookup_resolve(hostname))
|
47
|
-
return unless addresses
|
65
|
+
return false unless addresses
|
48
66
|
|
67
|
+
resolved = false
|
49
68
|
addresses.group_by(&:family).sort { |(f1, _), (f2, _)| f2 <=> f1 }.each do |family, addrs|
|
50
69
|
# try to match the resolver by family. However, there are cases where that's not possible, as when
|
51
70
|
# the system does not have IPv6 connectivity, but it does support IPv6 via loopback/link-local.
|
@@ -55,21 +74,20 @@ module HTTPX
|
|
55
74
|
|
56
75
|
# it does not matter which resolver it is, as early-resolve code is shared.
|
57
76
|
resolver.emit_addresses(connection, family, addrs, true)
|
58
|
-
end
|
59
|
-
end
|
60
77
|
|
61
|
-
|
78
|
+
resolved = true
|
79
|
+
end
|
62
80
|
|
63
|
-
|
64
|
-
emit(:resolve, connection)
|
81
|
+
resolved
|
65
82
|
end
|
66
83
|
|
67
|
-
def
|
68
|
-
|
69
|
-
|
84
|
+
def lazy_resolve(connection)
|
85
|
+
@resolvers.each do |resolver|
|
86
|
+
resolver << @current_session.try_clone_connection(connection, @current_selector, resolver.family)
|
87
|
+
next if resolver.empty?
|
70
88
|
|
71
|
-
|
72
|
-
|
89
|
+
@current_session.select_resolver(resolver, @current_selector)
|
90
|
+
end
|
73
91
|
end
|
74
92
|
end
|
75
93
|
end
|