httpx 1.2.6 → 1.4.4
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/README.md +1 -2
- data/doc/release_notes/1_3_0.md +18 -0
- data/doc/release_notes/1_3_1.md +17 -0
- data/doc/release_notes/1_3_2.md +6 -0
- data/doc/release_notes/1_3_3.md +5 -0
- data/doc/release_notes/1_3_4.md +6 -0
- data/doc/release_notes/1_4_0.md +43 -0
- data/doc/release_notes/1_4_1.md +19 -0
- data/doc/release_notes/1_4_2.md +20 -0
- data/doc/release_notes/1_4_3.md +11 -0
- data/doc/release_notes/1_4_4.md +14 -0
- data/lib/httpx/adapters/datadog.rb +56 -80
- data/lib/httpx/adapters/faraday.rb +5 -2
- data/lib/httpx/adapters/webmock.rb +24 -8
- data/lib/httpx/callbacks.rb +2 -7
- data/lib/httpx/chainable.rb +3 -1
- data/lib/httpx/connection/http1.rb +11 -7
- data/lib/httpx/connection/http2.rb +57 -34
- data/lib/httpx/connection.rb +270 -71
- data/lib/httpx/errors.rb +15 -4
- data/lib/httpx/io/ssl.rb +6 -3
- data/lib/httpx/io/tcp.rb +1 -1
- data/lib/httpx/io/unix.rb +1 -1
- data/lib/httpx/loggable.rb +17 -10
- data/lib/httpx/options.rb +30 -23
- data/lib/httpx/plugins/aws_sdk_authentication.rb +3 -0
- data/lib/httpx/plugins/aws_sigv4.rb +36 -17
- data/lib/httpx/plugins/callbacks.rb +13 -2
- data/lib/httpx/plugins/circuit_breaker.rb +11 -5
- data/lib/httpx/plugins/content_digest.rb +202 -0
- data/lib/httpx/plugins/cookies.rb +9 -6
- data/lib/httpx/plugins/digest_auth.rb +3 -0
- data/lib/httpx/plugins/expect.rb +10 -4
- data/lib/httpx/plugins/follow_redirects.rb +68 -33
- data/lib/httpx/plugins/grpc/grpc_encoding.rb +2 -0
- data/lib/httpx/plugins/grpc.rb +2 -2
- data/lib/httpx/plugins/h2c.rb +23 -20
- data/lib/httpx/plugins/internal_telemetry.rb +48 -1
- data/lib/httpx/plugins/oauth.rb +1 -1
- data/lib/httpx/plugins/persistent.rb +16 -0
- data/lib/httpx/plugins/proxy/http.rb +19 -16
- data/lib/httpx/plugins/proxy/socks4.rb +1 -1
- data/lib/httpx/plugins/proxy/socks5.rb +1 -1
- data/lib/httpx/plugins/proxy.rb +96 -85
- data/lib/httpx/plugins/retries.rb +28 -10
- data/lib/httpx/plugins/ssrf_filter.rb +4 -1
- data/lib/httpx/plugins/stream.rb +42 -18
- 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 +50 -55
- data/lib/httpx/request.rb +77 -14
- data/lib/httpx/resolver/https.rb +17 -20
- data/lib/httpx/resolver/multi.rb +34 -16
- data/lib/httpx/resolver/native.rb +140 -61
- data/lib/httpx/resolver/resolver.rb +64 -19
- data/lib/httpx/resolver/system.rb +32 -16
- data/lib/httpx/resolver.rb +21 -14
- data/lib/httpx/response/body.rb +12 -1
- data/lib/httpx/response.rb +16 -9
- data/lib/httpx/selector.rb +170 -91
- data/lib/httpx/session.rb +282 -139
- data/lib/httpx/timers.rb +17 -2
- data/lib/httpx/transcoder/body.rb +15 -29
- data/lib/httpx/transcoder/form.rb +2 -0
- data/lib/httpx/transcoder/gzip.rb +0 -3
- data/lib/httpx/transcoder/json.rb +16 -2
- data/lib/httpx/transcoder/multipart/encoder.rb +11 -2
- data/lib/httpx/transcoder/multipart/part.rb +1 -1
- data/lib/httpx/transcoder/utils/deflater.rb +7 -4
- data/lib/httpx/transcoder.rb +0 -1
- data/lib/httpx/version.rb +1 -1
- data/lib/httpx.rb +20 -21
- data/sig/callbacks.rbs +2 -3
- data/sig/chainable.rbs +6 -2
- data/sig/connection/http1.rbs +2 -2
- data/sig/connection/http2.rbs +22 -18
- data/sig/connection.rbs +40 -9
- data/sig/errors.rbs +9 -3
- data/sig/httpx.rbs +3 -3
- data/sig/io/tcp.rbs +1 -1
- data/sig/io/unix.rbs +1 -1
- data/sig/loggable.rbs +4 -2
- data/sig/options.rbs +8 -13
- 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/follow_redirects.rbs +1 -1
- data/sig/plugins/grpc/call.rbs +4 -0
- data/sig/plugins/persistent.rbs +4 -1
- data/sig/plugins/proxy/http.rbs +3 -0
- data/sig/plugins/proxy/socks5.rbs +11 -3
- data/sig/plugins/proxy.rbs +18 -9
- data/sig/plugins/push_promise.rbs +6 -3
- 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/stream.rbs +3 -0
- data/sig/plugins/webdav.rbs +23 -0
- data/sig/plugins/xml.rbs +37 -0
- data/sig/pool.rbs +27 -33
- data/sig/request/body.rbs +4 -10
- data/sig/request.rbs +14 -1
- data/sig/resolver/multi.rbs +26 -1
- data/sig/resolver/native.rbs +6 -3
- data/sig/resolver/resolver.rbs +22 -3
- data/sig/resolver.rbs +5 -1
- data/sig/response/body.rbs +2 -2
- data/sig/response/buffer.rbs +2 -2
- data/sig/response.rbs +9 -4
- data/sig/selector.rbs +31 -4
- data/sig/session.rbs +54 -20
- data/sig/timers.rbs +15 -4
- data/sig/transcoder/body.rbs +2 -4
- 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/json.rbs +1 -1
- data/sig/transcoder/multipart.rbs +6 -4
- data/sig/transcoder/utils/body_reader.rbs +3 -3
- data/sig/transcoder/utils/deflater.rbs +2 -3
- metadata +32 -14
- data/lib/httpx/session2.rb +0 -23
- data/lib/httpx/transcoder/utils/inflater.rb +0 -19
- data/lib/httpx/transcoder/xml.rb +0 -52
- data/sig/transcoder/utils/inflater.rbs +0 -12
- data/sig/transcoder/xml.rbs +0 -22
@@ -35,6 +35,7 @@ module HTTPX
|
|
35
35
|
@_timeouts = Array(@resolver_options[:timeouts])
|
36
36
|
@timeouts = Hash.new { |timeouts, host| timeouts[host] = @_timeouts.dup }
|
37
37
|
@connections = []
|
38
|
+
@name = nil
|
38
39
|
@queries = {}
|
39
40
|
@read_buffer = "".b
|
40
41
|
@write_buffer = Buffer.new(@resolver_options[:packet_size])
|
@@ -58,19 +59,6 @@ module HTTPX
|
|
58
59
|
when :open
|
59
60
|
consume
|
60
61
|
end
|
61
|
-
nil
|
62
|
-
rescue Errno::EHOSTUNREACH => e
|
63
|
-
@ns_index += 1
|
64
|
-
nameserver = @nameserver
|
65
|
-
if nameserver && @ns_index < nameserver.size
|
66
|
-
log { "resolver: failed resolving on nameserver #{@nameserver[@ns_index - 1]} (#{e.message})" }
|
67
|
-
transition(:idle)
|
68
|
-
@timeouts.clear
|
69
|
-
else
|
70
|
-
handle_error(e)
|
71
|
-
end
|
72
|
-
rescue NativeResolveError => e
|
73
|
-
handle_error(e)
|
74
62
|
end
|
75
63
|
|
76
64
|
def interests
|
@@ -105,9 +93,7 @@ module HTTPX
|
|
105
93
|
@timeouts.values_at(*hosts).reject(&:empty?).map(&:first).min
|
106
94
|
end
|
107
95
|
|
108
|
-
def handle_socket_timeout(interval)
|
109
|
-
do_retry(interval)
|
110
|
-
end
|
96
|
+
def handle_socket_timeout(interval); end
|
111
97
|
|
112
98
|
private
|
113
99
|
|
@@ -120,51 +106,94 @@ module HTTPX
|
|
120
106
|
end
|
121
107
|
|
122
108
|
def consume
|
123
|
-
|
124
|
-
|
125
|
-
|
109
|
+
loop do
|
110
|
+
dread if calculate_interests == :r
|
111
|
+
|
112
|
+
break unless calculate_interests == :w
|
113
|
+
|
114
|
+
# do_retry
|
115
|
+
dwrite
|
116
|
+
|
117
|
+
break unless calculate_interests == :r
|
118
|
+
end
|
119
|
+
rescue Errno::EHOSTUNREACH => e
|
120
|
+
@ns_index += 1
|
121
|
+
nameserver = @nameserver
|
122
|
+
if nameserver && @ns_index < nameserver.size
|
123
|
+
log do
|
124
|
+
"resolver #{FAMILY_TYPES[@record_type]}: " \
|
125
|
+
"failed resolving on nameserver #{@nameserver[@ns_index - 1]} (#{e.message})"
|
126
|
+
end
|
127
|
+
transition(:idle)
|
128
|
+
@timeouts.clear
|
129
|
+
retry
|
130
|
+
else
|
131
|
+
handle_error(e)
|
132
|
+
emit(:close, self)
|
133
|
+
end
|
134
|
+
rescue NativeResolveError => e
|
135
|
+
handle_error(e)
|
136
|
+
close_or_resolve
|
137
|
+
retry unless closed?
|
126
138
|
end
|
127
139
|
|
128
|
-
def
|
129
|
-
|
140
|
+
def schedule_retry
|
141
|
+
h = @name
|
130
142
|
|
131
|
-
|
143
|
+
return unless h
|
132
144
|
|
133
|
-
|
145
|
+
connection = @queries[h]
|
134
146
|
|
135
|
-
|
147
|
+
timeouts = @timeouts[h]
|
148
|
+
timeout = timeouts.shift
|
136
149
|
|
137
|
-
|
138
|
-
|
139
|
-
timeout = (@timeouts[host][0] -= loop_time)
|
150
|
+
@timer = @current_selector.after(timeout) do
|
151
|
+
next unless @connections.include?(connection)
|
140
152
|
|
141
|
-
|
153
|
+
do_retry(h, connection, timeout)
|
154
|
+
end
|
155
|
+
end
|
142
156
|
|
143
|
-
|
157
|
+
def do_retry(h, connection, interval)
|
158
|
+
timeouts = @timeouts[h]
|
144
159
|
|
145
|
-
if
|
146
|
-
log
|
160
|
+
if !timeouts.empty?
|
161
|
+
log do
|
162
|
+
"resolver #{FAMILY_TYPES[@record_type]}: timeout after #{interval}s, retry (with #{timeouts.first}s) #{h}..."
|
163
|
+
end
|
147
164
|
# must downgrade to tcp AND retry on same host as last
|
148
165
|
downgrade_socket
|
149
166
|
resolve(connection, h)
|
150
167
|
elsif @ns_index + 1 < @nameserver.size
|
151
168
|
# try on the next nameserver
|
152
169
|
@ns_index += 1
|
153
|
-
log
|
170
|
+
log do
|
171
|
+
"resolver #{FAMILY_TYPES[@record_type]}: failed resolving #{h} on nameserver #{@nameserver[@ns_index - 1]} (timeout error)"
|
172
|
+
end
|
154
173
|
transition(:idle)
|
155
174
|
@timeouts.clear
|
156
175
|
resolve(connection, h)
|
157
176
|
else
|
158
177
|
|
159
|
-
@timeouts.delete(
|
178
|
+
@timeouts.delete(h)
|
160
179
|
reset_hostname(h, reset_candidates: false)
|
161
180
|
|
162
|
-
|
181
|
+
unless @queries.empty?
|
182
|
+
resolve(connection)
|
183
|
+
return
|
184
|
+
end
|
163
185
|
|
164
186
|
@connections.delete(connection)
|
187
|
+
|
188
|
+
host = connection.peer.host
|
189
|
+
|
165
190
|
# This loop_time passed to the exception is bogus. Ideally we would pass the total
|
166
191
|
# resolve timeout, including from the previous retries.
|
167
|
-
|
192
|
+
ex = ResolveTimeoutError.new(interval, "Timed out while resolving #{host}")
|
193
|
+
ex.set_backtrace(ex ? ex.backtrace : caller)
|
194
|
+
emit_resolve_error(connection, host, ex)
|
195
|
+
|
196
|
+
close_or_resolve
|
168
197
|
end
|
169
198
|
end
|
170
199
|
|
@@ -213,7 +242,7 @@ module HTTPX
|
|
213
242
|
parse(@read_buffer)
|
214
243
|
end
|
215
244
|
|
216
|
-
return if @state == :closed
|
245
|
+
return if @state == :closed || !@write_buffer.empty?
|
217
246
|
end
|
218
247
|
end
|
219
248
|
|
@@ -231,11 +260,15 @@ module HTTPX
|
|
231
260
|
|
232
261
|
return unless siz.positive?
|
233
262
|
|
263
|
+
schedule_retry if @write_buffer.empty?
|
264
|
+
|
234
265
|
return if @state == :closed
|
235
266
|
end
|
236
267
|
end
|
237
268
|
|
238
269
|
def parse(buffer)
|
270
|
+
@timer.cancel
|
271
|
+
|
239
272
|
code, result = Resolver.decode_dns_answer(buffer)
|
240
273
|
|
241
274
|
case code
|
@@ -246,12 +279,17 @@ module HTTPX
|
|
246
279
|
hostname, connection = @queries.first
|
247
280
|
reset_hostname(hostname, reset_candidates: false)
|
248
281
|
|
249
|
-
|
282
|
+
other_candidate, _ = @queries.find { |_, conn| conn == connection }
|
283
|
+
|
284
|
+
if other_candidate
|
285
|
+
resolve(connection, other_candidate)
|
286
|
+
else
|
250
287
|
@connections.delete(connection)
|
251
|
-
|
288
|
+
ex = NativeResolveError.new(connection, connection.peer.host, "name or service not known")
|
289
|
+
ex.set_backtrace(ex ? ex.backtrace : caller)
|
290
|
+
emit_resolve_error(connection, connection.peer.host, ex)
|
291
|
+
close_or_resolve
|
252
292
|
end
|
253
|
-
|
254
|
-
resolve
|
255
293
|
when :message_truncated
|
256
294
|
# TODO: what to do if it's already tcp??
|
257
295
|
return if @socket_type == :tcp
|
@@ -265,13 +303,13 @@ module HTTPX
|
|
265
303
|
hostname, connection = @queries.first
|
266
304
|
reset_hostname(hostname)
|
267
305
|
@connections.delete(connection)
|
268
|
-
ex = NativeResolveError.new(connection, connection.
|
306
|
+
ex = NativeResolveError.new(connection, connection.peer.host, "unknown DNS error (error code #{result})")
|
269
307
|
raise ex
|
270
308
|
when :decode_error
|
271
309
|
hostname, connection = @queries.first
|
272
310
|
reset_hostname(hostname)
|
273
311
|
@connections.delete(connection)
|
274
|
-
ex = NativeResolveError.new(connection, connection.
|
312
|
+
ex = NativeResolveError.new(connection, connection.peer.host, result.message)
|
275
313
|
ex.set_backtrace(result.backtrace)
|
276
314
|
raise ex
|
277
315
|
end
|
@@ -283,7 +321,7 @@ module HTTPX
|
|
283
321
|
hostname, connection = @queries.first
|
284
322
|
reset_hostname(hostname)
|
285
323
|
@connections.delete(connection)
|
286
|
-
raise NativeResolveError.new(connection, connection.
|
324
|
+
raise NativeResolveError.new(connection, connection.peer.host)
|
287
325
|
else
|
288
326
|
address = addresses.first
|
289
327
|
name = address["name"]
|
@@ -306,12 +344,14 @@ module HTTPX
|
|
306
344
|
connection = @queries.delete(name)
|
307
345
|
end
|
308
346
|
|
309
|
-
|
310
|
-
|
347
|
+
alias_addresses, addresses = addresses.partition { |addr| addr.key?("alias") }
|
348
|
+
|
349
|
+
if addresses.empty? && !alias_addresses.empty? # CNAME
|
350
|
+
hostname_alias = alias_addresses.first["alias"]
|
311
351
|
# clean up intermediate queries
|
312
|
-
@timeouts.delete(name) unless connection.
|
352
|
+
@timeouts.delete(name) unless connection.peer.host == name
|
313
353
|
|
314
|
-
if
|
354
|
+
if early_resolve(connection, hostname: hostname_alias)
|
315
355
|
@connections.delete(connection)
|
316
356
|
else
|
317
357
|
if @socket_type == :tcp
|
@@ -320,24 +360,26 @@ module HTTPX
|
|
320
360
|
transition(:idle)
|
321
361
|
transition(:open)
|
322
362
|
end
|
323
|
-
log { "resolver: ALIAS #{hostname_alias} for #{name}" }
|
363
|
+
log { "resolver #{FAMILY_TYPES[@record_type]}: ALIAS #{hostname_alias} for #{name}" }
|
324
364
|
resolve(connection, hostname_alias)
|
325
365
|
return
|
326
366
|
end
|
327
367
|
else
|
328
368
|
reset_hostname(name, connection: connection)
|
329
|
-
@timeouts.delete(connection.
|
369
|
+
@timeouts.delete(connection.peer.host)
|
330
370
|
@connections.delete(connection)
|
331
|
-
Resolver.cached_lookup_set(connection.
|
332
|
-
emit_addresses(connection, @family, addresses.map { |addr| addr["data"] })
|
371
|
+
Resolver.cached_lookup_set(connection.peer.host, @family, addresses) if @resolver_options[:cache]
|
372
|
+
catch(:coalesced) { emit_addresses(connection, @family, addresses.map { |addr| addr["data"] }) }
|
333
373
|
end
|
334
374
|
end
|
335
|
-
|
336
|
-
|
337
|
-
resolve
|
375
|
+
close_or_resolve
|
338
376
|
end
|
339
377
|
|
340
|
-
def resolve(connection =
|
378
|
+
def resolve(connection = nil, hostname = nil)
|
379
|
+
@connections.shift until @connections.empty? || @connections.first.state != :closed
|
380
|
+
|
381
|
+
connection ||= @connections.find { |c| !@queries.value?(c) }
|
382
|
+
|
341
383
|
raise Error, "no URI to resolve" unless connection
|
342
384
|
|
343
385
|
return unless @write_buffer.empty?
|
@@ -345,8 +387,11 @@ module HTTPX
|
|
345
387
|
hostname ||= @queries.key(connection)
|
346
388
|
|
347
389
|
if hostname.nil?
|
348
|
-
hostname = connection.
|
349
|
-
log
|
390
|
+
hostname = connection.peer.host
|
391
|
+
log do
|
392
|
+
"resolver #{FAMILY_TYPES[@record_type]}: " \
|
393
|
+
"resolve IDN #{connection.peer.non_ascii_hostname} as #{hostname}"
|
394
|
+
end if connection.peer.non_ascii_hostname
|
350
395
|
|
351
396
|
hostname = generate_candidates(hostname).each do |name|
|
352
397
|
@queries[name] = connection
|
@@ -354,11 +399,17 @@ module HTTPX
|
|
354
399
|
else
|
355
400
|
@queries[hostname] = connection
|
356
401
|
end
|
357
|
-
|
402
|
+
|
403
|
+
@name = hostname
|
404
|
+
|
405
|
+
log { "resolver #{FAMILY_TYPES[@record_type]}: query for #{hostname}" }
|
358
406
|
begin
|
359
407
|
@write_buffer << encode_dns_query(hostname)
|
360
408
|
rescue Resolv::DNS::EncodeError => e
|
409
|
+
reset_hostname(hostname, connection: connection)
|
410
|
+
@connections.delete(connection)
|
361
411
|
emit_resolve_error(connection, hostname, e)
|
412
|
+
close_or_resolve
|
362
413
|
end
|
363
414
|
end
|
364
415
|
|
@@ -388,10 +439,10 @@ module HTTPX
|
|
388
439
|
|
389
440
|
case @socket_type
|
390
441
|
when :udp
|
391
|
-
log { "resolver: server: udp://#{ip}:#{port}..." }
|
442
|
+
log { "resolver #{FAMILY_TYPES[@record_type]}: server: udp://#{ip}:#{port}..." }
|
392
443
|
UDP.new(ip, port, @options)
|
393
444
|
when :tcp
|
394
|
-
log { "resolver: server: tcp://#{ip}:#{port}..." }
|
445
|
+
log { "resolver #{FAMILY_TYPES[@record_type]}: server: tcp://#{ip}:#{port}..." }
|
395
446
|
origin = URI("tcp://#{ip}:#{port}")
|
396
447
|
TCP.new(origin, [ip], @options)
|
397
448
|
end
|
@@ -430,14 +481,32 @@ module HTTPX
|
|
430
481
|
@read_buffer.clear
|
431
482
|
end
|
432
483
|
@state = nextstate
|
484
|
+
rescue Errno::ECONNREFUSED,
|
485
|
+
Errno::EADDRNOTAVAIL,
|
486
|
+
Errno::EHOSTUNREACH,
|
487
|
+
SocketError,
|
488
|
+
IOError,
|
489
|
+
ConnectTimeoutError => e
|
490
|
+
# these errors may happen during TCP handshake
|
491
|
+
# treat them as resolve errors.
|
492
|
+
handle_error(e)
|
493
|
+
emit(:close, self)
|
433
494
|
end
|
434
495
|
|
435
496
|
def handle_error(error)
|
436
497
|
if error.respond_to?(:connection) &&
|
437
498
|
error.respond_to?(:host)
|
499
|
+
reset_hostname(error.host, connection: error.connection)
|
500
|
+
@connections.delete(error.connection)
|
438
501
|
emit_resolve_error(error.connection, error.host, error)
|
439
502
|
else
|
440
503
|
@queries.each do |host, connection|
|
504
|
+
reset_hostname(host, connection: connection)
|
505
|
+
@connections.delete(connection)
|
506
|
+
emit_resolve_error(connection, host, error)
|
507
|
+
end
|
508
|
+
|
509
|
+
while (connection = @connections.shift)
|
441
510
|
emit_resolve_error(connection, host, error)
|
442
511
|
end
|
443
512
|
end
|
@@ -445,7 +514,6 @@ module HTTPX
|
|
445
514
|
|
446
515
|
def reset_hostname(hostname, connection: @queries.delete(hostname), reset_candidates: true)
|
447
516
|
@timeouts.delete(hostname)
|
448
|
-
@timeouts.delete(hostname)
|
449
517
|
|
450
518
|
return unless connection && reset_candidates
|
451
519
|
|
@@ -455,5 +523,16 @@ module HTTPX
|
|
455
523
|
# reset timeouts
|
456
524
|
@timeouts.delete_if { |h, _| candidates.include?(h) }
|
457
525
|
end
|
526
|
+
|
527
|
+
def close_or_resolve
|
528
|
+
# drop already closed connections
|
529
|
+
@connections.shift until @connections.empty? || @connections.first.state != :closed
|
530
|
+
|
531
|
+
if (@connections - @queries.values).empty?
|
532
|
+
emit(:close, self)
|
533
|
+
else
|
534
|
+
resolve
|
535
|
+
end
|
536
|
+
end
|
458
537
|
end
|
459
538
|
end
|
@@ -26,14 +26,26 @@ module HTTPX
|
|
26
26
|
end
|
27
27
|
end
|
28
28
|
|
29
|
-
attr_reader :family
|
29
|
+
attr_reader :family, :options
|
30
30
|
|
31
|
-
attr_writer :
|
31
|
+
attr_writer :current_selector, :current_session
|
32
|
+
|
33
|
+
attr_accessor :multi
|
32
34
|
|
33
35
|
def initialize(family, options)
|
34
36
|
@family = family
|
35
37
|
@record_type = RECORD_TYPES[family]
|
36
|
-
@options =
|
38
|
+
@options = options
|
39
|
+
|
40
|
+
set_resolver_callbacks
|
41
|
+
end
|
42
|
+
|
43
|
+
def each_connection(&block)
|
44
|
+
enum_for(__method__) unless block
|
45
|
+
|
46
|
+
return unless @connections
|
47
|
+
|
48
|
+
@connections.each(&block)
|
37
49
|
end
|
38
50
|
|
39
51
|
def close; end
|
@@ -48,6 +60,10 @@ module HTTPX
|
|
48
60
|
true
|
49
61
|
end
|
50
62
|
|
63
|
+
def inflight?
|
64
|
+
false
|
65
|
+
end
|
66
|
+
|
51
67
|
def emit_addresses(connection, family, addresses, early_resolve = false)
|
52
68
|
addresses.map! do |address|
|
53
69
|
address.is_a?(IPAddr) ? address : IPAddr.new(address.to_s)
|
@@ -56,17 +72,22 @@ module HTTPX
|
|
56
72
|
# double emission check, but allow early resolution to work
|
57
73
|
return if !early_resolve && connection.addresses && !addresses.intersect?(connection.addresses)
|
58
74
|
|
59
|
-
log
|
60
|
-
|
61
|
-
|
62
|
-
|
63
|
-
|
64
|
-
|
65
|
-
|
66
|
-
|
67
|
-
|
68
|
-
|
69
|
-
|
75
|
+
log do
|
76
|
+
"resolver #{FAMILY_TYPES[RECORD_TYPES[family]]}: " \
|
77
|
+
"answer #{connection.peer.host}: #{addresses.inspect} (early resolve: #{early_resolve})"
|
78
|
+
end
|
79
|
+
|
80
|
+
if !early_resolve && # do not apply resolution delay for non-dns name resolution
|
81
|
+
@current_selector && # just in case...
|
82
|
+
family == Socket::AF_INET && # resolution delay only applies to IPv4
|
83
|
+
!connection.io && # connection already has addresses and initiated/ended handshake
|
84
|
+
connection.options.ip_families.size > 1 && # no need to delay if not supporting dual stack IP
|
85
|
+
addresses.first.to_s != connection.peer.host.to_s # connection URL host is already the IP (early resolve included perhaps?)
|
86
|
+
log { "resolver #{FAMILY_TYPES[RECORD_TYPES[family]]}: applying resolution delay..." }
|
87
|
+
|
88
|
+
@current_selector.after(0.05) do
|
89
|
+
# double emission check
|
90
|
+
unless connection.addresses && addresses.intersect?(connection.addresses)
|
70
91
|
emit_resolved_connection(connection, addresses, early_resolve)
|
71
92
|
end
|
72
93
|
end
|
@@ -81,6 +102,8 @@ module HTTPX
|
|
81
102
|
begin
|
82
103
|
connection.addresses = addresses
|
83
104
|
|
105
|
+
return if connection.state == :closed
|
106
|
+
|
84
107
|
emit(:resolve, connection)
|
85
108
|
rescue StandardError => e
|
86
109
|
if early_resolve
|
@@ -92,20 +115,22 @@ module HTTPX
|
|
92
115
|
end
|
93
116
|
end
|
94
117
|
|
95
|
-
def early_resolve(connection, hostname: connection.
|
118
|
+
def early_resolve(connection, hostname: connection.peer.host)
|
96
119
|
addresses = @resolver_options[:cache] && (connection.addresses || HTTPX::Resolver.nolookup_resolve(hostname))
|
97
120
|
|
98
|
-
return unless addresses
|
121
|
+
return false unless addresses
|
99
122
|
|
100
123
|
addresses = addresses.select { |addr| addr.family == @family }
|
101
124
|
|
102
|
-
return if addresses.empty?
|
125
|
+
return false if addresses.empty?
|
103
126
|
|
104
127
|
emit_addresses(connection, @family, addresses, true)
|
128
|
+
|
129
|
+
true
|
105
130
|
end
|
106
131
|
|
107
|
-
def emit_resolve_error(connection, hostname = connection.
|
108
|
-
|
132
|
+
def emit_resolve_error(connection, hostname = connection.peer.host, ex = nil)
|
133
|
+
emit_connection_error(connection, resolve_error(hostname, ex))
|
109
134
|
end
|
110
135
|
|
111
136
|
def resolve_error(hostname, ex = nil)
|
@@ -116,5 +141,25 @@ module HTTPX
|
|
116
141
|
error.set_backtrace(ex ? ex.backtrace : caller)
|
117
142
|
error
|
118
143
|
end
|
144
|
+
|
145
|
+
def set_resolver_callbacks
|
146
|
+
on(:resolve, &method(:resolve_connection))
|
147
|
+
on(:error, &method(:emit_connection_error))
|
148
|
+
on(:close, &method(:close_resolver))
|
149
|
+
end
|
150
|
+
|
151
|
+
def resolve_connection(connection)
|
152
|
+
@current_session.__send__(:on_resolver_connection, connection, @current_selector)
|
153
|
+
end
|
154
|
+
|
155
|
+
def emit_connection_error(connection, error)
|
156
|
+
return connection.handle_connect_error(error) if connection.connecting?
|
157
|
+
|
158
|
+
connection.emit(:error, error)
|
159
|
+
end
|
160
|
+
|
161
|
+
def close_resolver(resolver)
|
162
|
+
@current_session.__send__(:on_resolver_close, resolver, @current_selector)
|
163
|
+
end
|
119
164
|
end
|
120
165
|
end
|
@@ -1,12 +1,10 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
-
require "forwardable"
|
4
3
|
require "resolv"
|
5
4
|
|
6
5
|
module HTTPX
|
7
6
|
class Resolver::System < Resolver::Resolver
|
8
7
|
using URIExtensions
|
9
|
-
extend Forwardable
|
10
8
|
|
11
9
|
RESOLV_ERRORS = [Resolv::ResolvError,
|
12
10
|
Resolv::DNS::Requester::RequestError,
|
@@ -24,8 +22,6 @@ module HTTPX
|
|
24
22
|
|
25
23
|
attr_reader :state
|
26
24
|
|
27
|
-
def_delegator :@connections, :empty?
|
28
|
-
|
29
25
|
def initialize(options)
|
30
26
|
super(nil, options)
|
31
27
|
@resolver_options = @options.resolver_options
|
@@ -47,8 +43,12 @@ module HTTPX
|
|
47
43
|
yield self
|
48
44
|
end
|
49
45
|
|
50
|
-
def
|
51
|
-
|
46
|
+
def multi
|
47
|
+
self
|
48
|
+
end
|
49
|
+
|
50
|
+
def empty?
|
51
|
+
true
|
52
52
|
end
|
53
53
|
|
54
54
|
def close
|
@@ -84,7 +84,7 @@ module HTTPX
|
|
84
84
|
|
85
85
|
return unless connection
|
86
86
|
|
87
|
-
@timeouts[connection.
|
87
|
+
@timeouts[connection.peer.host].first
|
88
88
|
end
|
89
89
|
|
90
90
|
def <<(connection)
|
@@ -92,6 +92,11 @@ module HTTPX
|
|
92
92
|
resolve
|
93
93
|
end
|
94
94
|
|
95
|
+
def early_resolve(connection, **)
|
96
|
+
self << connection
|
97
|
+
true
|
98
|
+
end
|
99
|
+
|
95
100
|
def handle_socket_timeout(interval)
|
96
101
|
error = HTTPX::ResolveTimeoutError.new(interval, "timed out while waiting on select")
|
97
102
|
error.set_backtrace(caller)
|
@@ -120,23 +125,26 @@ module HTTPX
|
|
120
125
|
def consume
|
121
126
|
return if @connections.empty?
|
122
127
|
|
123
|
-
|
128
|
+
if @pipe_read.wait_readable
|
129
|
+
event = @pipe_read.getbyte
|
130
|
+
|
124
131
|
case event
|
125
132
|
when DONE
|
126
133
|
*pair, addrs = @pipe_mutex.synchronize { @ips.pop }
|
127
134
|
@queries.delete(pair)
|
135
|
+
_, connection = pair
|
136
|
+
@connections.delete(connection)
|
128
137
|
|
129
138
|
family, connection = pair
|
130
|
-
emit_addresses(connection, family, addrs)
|
139
|
+
catch(:coalesced) { emit_addresses(connection, family, addrs) }
|
131
140
|
when ERROR
|
132
141
|
*pair, error = @pipe_mutex.synchronize { @ips.pop }
|
133
142
|
@queries.delete(pair)
|
143
|
+
@connections.delete(connection)
|
134
144
|
|
135
|
-
|
136
|
-
emit_resolve_error(connection, connection.
|
145
|
+
_, connection = pair
|
146
|
+
emit_resolve_error(connection, connection.peer.host, error)
|
137
147
|
end
|
138
|
-
|
139
|
-
@connections.delete(connection) if @queries.empty?
|
140
148
|
end
|
141
149
|
|
142
150
|
return emit(:close, self) if @connections.empty?
|
@@ -148,9 +156,11 @@ module HTTPX
|
|
148
156
|
raise Error, "no URI to resolve" unless connection
|
149
157
|
return unless @queries.empty?
|
150
158
|
|
151
|
-
hostname = connection.
|
159
|
+
hostname = connection.peer.host
|
152
160
|
scheme = connection.origin.scheme
|
153
|
-
log
|
161
|
+
log do
|
162
|
+
"resolver: resolve IDN #{connection.peer.non_ascii_hostname} as #{hostname}"
|
163
|
+
end if connection.peer.non_ascii_hostname
|
154
164
|
|
155
165
|
transition(:open)
|
156
166
|
|
@@ -164,7 +174,7 @@ module HTTPX
|
|
164
174
|
def async_resolve(connection, hostname, scheme)
|
165
175
|
families = connection.options.ip_families
|
166
176
|
log { "resolver: query for #{hostname}" }
|
167
|
-
timeouts = @timeouts[connection.
|
177
|
+
timeouts = @timeouts[connection.peer.host]
|
168
178
|
resolve_timeout = timeouts.first
|
169
179
|
|
170
180
|
Thread.start do
|
@@ -210,5 +220,11 @@ module HTTPX
|
|
210
220
|
def __addrinfo_resolve(host, scheme)
|
211
221
|
Addrinfo.getaddrinfo(host, scheme, Socket::AF_UNSPEC, Socket::SOCK_STREAM)
|
212
222
|
end
|
223
|
+
|
224
|
+
def emit_connection_error(_, error)
|
225
|
+
throw(:resolve_error, error)
|
226
|
+
end
|
227
|
+
|
228
|
+
def close_resolver(resolver); end
|
213
229
|
end
|
214
230
|
end
|