httpx 1.3.4 → 1.4.0
Sign up to get free protection for your applications and to get access to all the features.
- 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
|