httpx 1.3.4 → 1.4.1
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/doc/release_notes/1_4_1.md +19 -0
- data/lib/httpx/adapters/datadog.rb +55 -83
- data/lib/httpx/adapters/faraday.rb +2 -0
- data/lib/httpx/adapters/webmock.rb +18 -6
- data/lib/httpx/callbacks.rb +0 -5
- data/lib/httpx/chainable.rb +3 -1
- data/lib/httpx/connection/http2.rb +12 -8
- data/lib/httpx/connection.rb +192 -22
- 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/grpc/grpc_encoding.rb +2 -0
- 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 +25 -26
- data/lib/httpx/request.rb +7 -1
- data/lib/httpx/resolver/https.rb +15 -20
- data/lib/httpx/resolver/multi.rb +34 -16
- data/lib/httpx/resolver/native.rb +66 -25
- data/lib/httpx/resolver/resolver.rb +59 -15
- data/lib/httpx/resolver/system.rb +31 -15
- data/lib/httpx/resolver.rb +21 -14
- data/lib/httpx/response.rb +5 -3
- data/lib/httpx/selector.rb +160 -95
- data/lib/httpx/session.rb +273 -140
- data/lib/httpx/transcoder/body.rb +15 -31
- data/lib/httpx/transcoder/gzip.rb +0 -3
- data/lib/httpx/transcoder/json.rb +14 -2
- data/lib/httpx/transcoder/multipart/part.rb +1 -1
- 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 +20 -21
- data/sig/callbacks.rbs +0 -1
- data/sig/chainable.rbs +4 -0
- data/sig/connection/http2.rbs +1 -1
- data/sig/connection.rbs +29 -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 -9
- data/sig/resolver/multi.rbs +26 -1
- data/sig/resolver/native.rbs +2 -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 +47 -18
- 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/utils/body_reader.rbs +3 -3
- data/sig/transcoder/utils/deflater.rbs +3 -3
- metadata +12 -4
- data/lib/httpx/transcoder/xml.rb +0 -52
- data/sig/transcoder/xml.rbs +0 -22
@@ -63,7 +63,10 @@ module HTTPX
|
|
63
63
|
@ns_index += 1
|
64
64
|
nameserver = @nameserver
|
65
65
|
if nameserver && @ns_index < nameserver.size
|
66
|
-
log
|
66
|
+
log do
|
67
|
+
"resolver #{FAMILY_TYPES[@record_type]}: " \
|
68
|
+
"failed resolving on nameserver #{@nameserver[@ns_index - 1]} (#{e.message})"
|
69
|
+
end
|
67
70
|
transition(:idle)
|
68
71
|
@timeouts.clear
|
69
72
|
else
|
@@ -135,22 +138,27 @@ module HTTPX
|
|
135
138
|
return unless query
|
136
139
|
|
137
140
|
h, connection = query
|
138
|
-
host = connection.
|
141
|
+
host = connection.peer.host
|
139
142
|
timeout = (@timeouts[host][0] -= loop_time)
|
140
143
|
|
141
144
|
return unless timeout <= 0
|
142
145
|
|
146
|
+
elapsed_after = @_timeouts[@_timeouts.size - @timeouts[host].size]
|
143
147
|
@timeouts[host].shift
|
144
148
|
|
145
149
|
if !@timeouts[host].empty?
|
146
|
-
log
|
150
|
+
log do
|
151
|
+
"resolver #{FAMILY_TYPES[@record_type]}: timeout after #{elapsed_after}s, retry (with #{@timeouts[host].first}s) #{host}..."
|
152
|
+
end
|
147
153
|
# must downgrade to tcp AND retry on same host as last
|
148
154
|
downgrade_socket
|
149
155
|
resolve(connection, h)
|
150
156
|
elsif @ns_index + 1 < @nameserver.size
|
151
157
|
# try on the next nameserver
|
152
158
|
@ns_index += 1
|
153
|
-
log
|
159
|
+
log do
|
160
|
+
"resolver #{FAMILY_TYPES[@record_type]}: failed resolving #{host} on nameserver #{@nameserver[@ns_index - 1]} (timeout error)"
|
161
|
+
end
|
154
162
|
transition(:idle)
|
155
163
|
@timeouts.clear
|
156
164
|
resolve(connection, h)
|
@@ -164,7 +172,11 @@ module HTTPX
|
|
164
172
|
@connections.delete(connection)
|
165
173
|
# This loop_time passed to the exception is bogus. Ideally we would pass the total
|
166
174
|
# resolve timeout, including from the previous retries.
|
167
|
-
|
175
|
+
ex = ResolveTimeoutError.new(loop_time, "Timed out while resolving #{connection.peer.host}")
|
176
|
+
ex.set_backtrace(ex ? ex.backtrace : caller)
|
177
|
+
emit_resolve_error(connection, host, ex)
|
178
|
+
|
179
|
+
close_or_resolve
|
168
180
|
end
|
169
181
|
end
|
170
182
|
|
@@ -246,12 +258,15 @@ module HTTPX
|
|
246
258
|
hostname, connection = @queries.first
|
247
259
|
reset_hostname(hostname, reset_candidates: false)
|
248
260
|
|
249
|
-
|
261
|
+
if @queries.value?(connection)
|
262
|
+
resolve
|
263
|
+
else
|
250
264
|
@connections.delete(connection)
|
251
|
-
|
265
|
+
ex = NativeResolveError.new(connection, connection.peer.host, "name or service not known")
|
266
|
+
ex.set_backtrace(ex ? ex.backtrace : caller)
|
267
|
+
emit_resolve_error(connection, connection.peer.host, ex)
|
268
|
+
close_or_resolve
|
252
269
|
end
|
253
|
-
|
254
|
-
resolve
|
255
270
|
when :message_truncated
|
256
271
|
# TODO: what to do if it's already tcp??
|
257
272
|
return if @socket_type == :tcp
|
@@ -265,13 +280,13 @@ module HTTPX
|
|
265
280
|
hostname, connection = @queries.first
|
266
281
|
reset_hostname(hostname)
|
267
282
|
@connections.delete(connection)
|
268
|
-
ex = NativeResolveError.new(connection, connection.
|
283
|
+
ex = NativeResolveError.new(connection, connection.peer.host, "unknown DNS error (error code #{result})")
|
269
284
|
raise ex
|
270
285
|
when :decode_error
|
271
286
|
hostname, connection = @queries.first
|
272
287
|
reset_hostname(hostname)
|
273
288
|
@connections.delete(connection)
|
274
|
-
ex = NativeResolveError.new(connection, connection.
|
289
|
+
ex = NativeResolveError.new(connection, connection.peer.host, result.message)
|
275
290
|
ex.set_backtrace(result.backtrace)
|
276
291
|
raise ex
|
277
292
|
end
|
@@ -283,7 +298,7 @@ module HTTPX
|
|
283
298
|
hostname, connection = @queries.first
|
284
299
|
reset_hostname(hostname)
|
285
300
|
@connections.delete(connection)
|
286
|
-
raise NativeResolveError.new(connection, connection.
|
301
|
+
raise NativeResolveError.new(connection, connection.peer.host)
|
287
302
|
else
|
288
303
|
address = addresses.first
|
289
304
|
name = address["name"]
|
@@ -309,9 +324,9 @@ module HTTPX
|
|
309
324
|
if address.key?("alias") # CNAME
|
310
325
|
hostname_alias = address["alias"]
|
311
326
|
# clean up intermediate queries
|
312
|
-
@timeouts.delete(name) unless connection.
|
327
|
+
@timeouts.delete(name) unless connection.peer.host == name
|
313
328
|
|
314
|
-
if
|
329
|
+
if early_resolve(connection, hostname: hostname_alias)
|
315
330
|
@connections.delete(connection)
|
316
331
|
else
|
317
332
|
if @socket_type == :tcp
|
@@ -320,21 +335,19 @@ module HTTPX
|
|
320
335
|
transition(:idle)
|
321
336
|
transition(:open)
|
322
337
|
end
|
323
|
-
log { "resolver: ALIAS #{hostname_alias} for #{name}" }
|
338
|
+
log { "resolver #{FAMILY_TYPES[@record_type]}: ALIAS #{hostname_alias} for #{name}" }
|
324
339
|
resolve(connection, hostname_alias)
|
325
340
|
return
|
326
341
|
end
|
327
342
|
else
|
328
343
|
reset_hostname(name, connection: connection)
|
329
|
-
@timeouts.delete(connection.
|
344
|
+
@timeouts.delete(connection.peer.host)
|
330
345
|
@connections.delete(connection)
|
331
|
-
Resolver.cached_lookup_set(connection.
|
346
|
+
Resolver.cached_lookup_set(connection.peer.host, @family, addresses) if @resolver_options[:cache]
|
332
347
|
catch(:coalesced) { emit_addresses(connection, @family, addresses.map { |addr| addr["data"] }) }
|
333
348
|
end
|
334
349
|
end
|
335
|
-
|
336
|
-
|
337
|
-
resolve
|
350
|
+
close_or_resolve
|
338
351
|
end
|
339
352
|
|
340
353
|
def resolve(connection = @connections.first, hostname = nil)
|
@@ -345,8 +358,11 @@ module HTTPX
|
|
345
358
|
hostname ||= @queries.key(connection)
|
346
359
|
|
347
360
|
if hostname.nil?
|
348
|
-
hostname = connection.
|
349
|
-
log
|
361
|
+
hostname = connection.peer.host
|
362
|
+
log do
|
363
|
+
"resolver #{FAMILY_TYPES[@record_type]}: " \
|
364
|
+
"resolve IDN #{connection.peer.non_ascii_hostname} as #{hostname}"
|
365
|
+
end if connection.peer.non_ascii_hostname
|
350
366
|
|
351
367
|
hostname = generate_candidates(hostname).each do |name|
|
352
368
|
@queries[name] = connection
|
@@ -354,11 +370,14 @@ module HTTPX
|
|
354
370
|
else
|
355
371
|
@queries[hostname] = connection
|
356
372
|
end
|
357
|
-
log { "resolver
|
373
|
+
log { "resolver #{FAMILY_TYPES[@record_type]}: query for #{hostname}" }
|
358
374
|
begin
|
359
375
|
@write_buffer << encode_dns_query(hostname)
|
360
376
|
rescue Resolv::DNS::EncodeError => e
|
377
|
+
reset_hostname(hostname, connection: connection)
|
378
|
+
@connections.delete(connection)
|
361
379
|
emit_resolve_error(connection, hostname, e)
|
380
|
+
close_or_resolve
|
362
381
|
end
|
363
382
|
end
|
364
383
|
|
@@ -388,10 +407,10 @@ module HTTPX
|
|
388
407
|
|
389
408
|
case @socket_type
|
390
409
|
when :udp
|
391
|
-
log { "resolver: server: udp://#{ip}:#{port}..." }
|
410
|
+
log { "resolver #{FAMILY_TYPES[@record_type]}: server: udp://#{ip}:#{port}..." }
|
392
411
|
UDP.new(ip, port, @options)
|
393
412
|
when :tcp
|
394
|
-
log { "resolver: server: tcp://#{ip}:#{port}..." }
|
413
|
+
log { "resolver #{FAMILY_TYPES[@record_type]}: server: tcp://#{ip}:#{port}..." }
|
395
414
|
origin = URI("tcp://#{ip}:#{port}")
|
396
415
|
TCP.new(origin, [ip], @options)
|
397
416
|
end
|
@@ -430,17 +449,31 @@ module HTTPX
|
|
430
449
|
@read_buffer.clear
|
431
450
|
end
|
432
451
|
@state = nextstate
|
452
|
+
rescue Errno::ECONNREFUSED,
|
453
|
+
Errno::EADDRNOTAVAIL,
|
454
|
+
Errno::EHOSTUNREACH,
|
455
|
+
SocketError,
|
456
|
+
IOError,
|
457
|
+
ConnectTimeoutError => e
|
458
|
+
# these errors may happen during TCP handshake
|
459
|
+
# treat them as resolve errors.
|
460
|
+
handle_error(e)
|
433
461
|
end
|
434
462
|
|
435
463
|
def handle_error(error)
|
436
464
|
if error.respond_to?(:connection) &&
|
437
465
|
error.respond_to?(:host)
|
466
|
+
reset_hostname(error.host, connection: error.connection)
|
467
|
+
@connections.delete(error.connection)
|
438
468
|
emit_resolve_error(error.connection, error.host, error)
|
439
469
|
else
|
440
470
|
@queries.each do |host, connection|
|
471
|
+
reset_hostname(host, connection: connection)
|
472
|
+
@connections.delete(connection)
|
441
473
|
emit_resolve_error(connection, host, error)
|
442
474
|
end
|
443
475
|
end
|
476
|
+
close_or_resolve
|
444
477
|
end
|
445
478
|
|
446
479
|
def reset_hostname(hostname, connection: @queries.delete(hostname), reset_candidates: true)
|
@@ -455,5 +488,13 @@ module HTTPX
|
|
455
488
|
# reset timeouts
|
456
489
|
@timeouts.delete_if { |h, _| candidates.include?(h) }
|
457
490
|
end
|
491
|
+
|
492
|
+
def close_or_resolve
|
493
|
+
if @connections.empty?
|
494
|
+
emit(:close, self)
|
495
|
+
else
|
496
|
+
resolve
|
497
|
+
end
|
498
|
+
end
|
458
499
|
end
|
459
500
|
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
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,21 @@ 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
|
-
|
75
|
+
log do
|
76
|
+
"resolver #{FAMILY_TYPES[RECORD_TYPES[family]]}: " \
|
77
|
+
"answer #{FAMILY_TYPES[RECORD_TYPES[family]]} #{connection.peer.host}: #{addresses.inspect}"
|
78
|
+
end
|
79
|
+
|
80
|
+
if @current_selector && # if triggered by early resolve, session may not be here yet
|
61
81
|
!connection.io &&
|
62
82
|
connection.options.ip_families.size > 1 &&
|
63
83
|
family == Socket::AF_INET &&
|
64
|
-
addresses.first.to_s != connection.
|
65
|
-
log { "resolver:
|
66
|
-
|
67
|
-
|
68
|
-
|
69
|
-
|
84
|
+
addresses.first.to_s != connection.peer.host.to_s
|
85
|
+
log { "resolver #{FAMILY_TYPES[RECORD_TYPES[family]]}: applying resolution delay..." }
|
86
|
+
|
87
|
+
@current_selector.after(0.05) do
|
88
|
+
# double emission check
|
89
|
+
unless connection.addresses && addresses.intersect?(connection.addresses)
|
70
90
|
emit_resolved_connection(connection, addresses, early_resolve)
|
71
91
|
end
|
72
92
|
end
|
@@ -81,6 +101,8 @@ module HTTPX
|
|
81
101
|
begin
|
82
102
|
connection.addresses = addresses
|
83
103
|
|
104
|
+
return if connection.state == :closed
|
105
|
+
|
84
106
|
emit(:resolve, connection)
|
85
107
|
rescue StandardError => e
|
86
108
|
if early_resolve
|
@@ -92,20 +114,22 @@ module HTTPX
|
|
92
114
|
end
|
93
115
|
end
|
94
116
|
|
95
|
-
def early_resolve(connection, hostname: connection.
|
117
|
+
def early_resolve(connection, hostname: connection.peer.host)
|
96
118
|
addresses = @resolver_options[:cache] && (connection.addresses || HTTPX::Resolver.nolookup_resolve(hostname))
|
97
119
|
|
98
|
-
return unless addresses
|
120
|
+
return false unless addresses
|
99
121
|
|
100
122
|
addresses = addresses.select { |addr| addr.family == @family }
|
101
123
|
|
102
|
-
return if addresses.empty?
|
124
|
+
return false if addresses.empty?
|
103
125
|
|
104
126
|
emit_addresses(connection, @family, addresses, true)
|
127
|
+
|
128
|
+
true
|
105
129
|
end
|
106
130
|
|
107
|
-
def emit_resolve_error(connection, hostname = connection.
|
108
|
-
|
131
|
+
def emit_resolve_error(connection, hostname = connection.peer.host, ex = nil)
|
132
|
+
emit_connection_error(connection, resolve_error(hostname, ex))
|
109
133
|
end
|
110
134
|
|
111
135
|
def resolve_error(hostname, ex = nil)
|
@@ -116,5 +140,25 @@ module HTTPX
|
|
116
140
|
error.set_backtrace(ex ? ex.backtrace : caller)
|
117
141
|
error
|
118
142
|
end
|
143
|
+
|
144
|
+
def set_resolver_callbacks
|
145
|
+
on(:resolve, &method(:resolve_connection))
|
146
|
+
on(:error, &method(:emit_connection_error))
|
147
|
+
on(:close, &method(:close_resolver))
|
148
|
+
end
|
149
|
+
|
150
|
+
def resolve_connection(connection)
|
151
|
+
@current_session.__send__(:on_resolver_connection, connection, @current_selector)
|
152
|
+
end
|
153
|
+
|
154
|
+
def emit_connection_error(connection, error)
|
155
|
+
return connection.handle_connect_error(error) if connection.connecting?
|
156
|
+
|
157
|
+
connection.emit(:error, error)
|
158
|
+
end
|
159
|
+
|
160
|
+
def close_resolver(resolver)
|
161
|
+
@current_session.__send__(:on_resolver_close, resolver, @current_selector)
|
162
|
+
end
|
119
163
|
end
|
120
164
|
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
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
|
data/lib/httpx/resolver.rb
CHANGED
@@ -53,8 +53,8 @@ module HTTPX
|
|
53
53
|
|
54
54
|
def cached_lookup(hostname)
|
55
55
|
now = Utils.now
|
56
|
-
|
57
|
-
lookup(hostname, now)
|
56
|
+
lookup_synchronize do |lookups|
|
57
|
+
lookup(hostname, lookups, now)
|
58
58
|
end
|
59
59
|
end
|
60
60
|
|
@@ -63,37 +63,37 @@ module HTTPX
|
|
63
63
|
entries.each do |entry|
|
64
64
|
entry["TTL"] += now
|
65
65
|
end
|
66
|
-
|
66
|
+
lookup_synchronize do |lookups|
|
67
67
|
case family
|
68
68
|
when Socket::AF_INET6
|
69
|
-
|
69
|
+
lookups[hostname].concat(entries)
|
70
70
|
when Socket::AF_INET
|
71
|
-
|
71
|
+
lookups[hostname].unshift(*entries)
|
72
72
|
end
|
73
73
|
entries.each do |entry|
|
74
74
|
next unless entry["name"] != hostname
|
75
75
|
|
76
76
|
case family
|
77
77
|
when Socket::AF_INET6
|
78
|
-
|
78
|
+
lookups[entry["name"]] << entry
|
79
79
|
when Socket::AF_INET
|
80
|
-
|
80
|
+
lookups[entry["name"]].unshift(entry)
|
81
81
|
end
|
82
82
|
end
|
83
83
|
end
|
84
84
|
end
|
85
85
|
|
86
86
|
# do not use directly!
|
87
|
-
def lookup(hostname, ttl)
|
88
|
-
return unless
|
87
|
+
def lookup(hostname, lookups, ttl)
|
88
|
+
return unless lookups.key?(hostname)
|
89
89
|
|
90
|
-
entries =
|
90
|
+
entries = lookups[hostname] = lookups[hostname].select do |address|
|
91
91
|
address["TTL"] > ttl
|
92
92
|
end
|
93
93
|
|
94
94
|
ips = entries.flat_map do |address|
|
95
95
|
if address.key?("alias")
|
96
|
-
lookup(address["alias"], ttl)
|
96
|
+
lookup(address["alias"], lookups, ttl)
|
97
97
|
else
|
98
98
|
IPAddr.new(address["data"])
|
99
99
|
end
|
@@ -103,12 +103,11 @@ module HTTPX
|
|
103
103
|
end
|
104
104
|
|
105
105
|
def generate_id
|
106
|
-
|
106
|
+
id_synchronize { @identifier = (@identifier + 1) & 0xFFFF }
|
107
107
|
end
|
108
108
|
|
109
109
|
def encode_dns_query(hostname, type: Resolv::DNS::Resource::IN::A, message_id: generate_id)
|
110
|
-
Resolv::DNS::Message.new.tap do |query|
|
111
|
-
query.id = message_id
|
110
|
+
Resolv::DNS::Message.new(message_id).tap do |query|
|
112
111
|
query.rd = 1
|
113
112
|
query.add_question(hostname, type)
|
114
113
|
end.encode
|
@@ -150,5 +149,13 @@ module HTTPX
|
|
150
149
|
|
151
150
|
[:ok, addresses]
|
152
151
|
end
|
152
|
+
|
153
|
+
def lookup_synchronize
|
154
|
+
@lookup_mutex.synchronize { yield(@lookups) }
|
155
|
+
end
|
156
|
+
|
157
|
+
def id_synchronize(&block)
|
158
|
+
@identifier_mutex.synchronize(&block)
|
159
|
+
end
|
153
160
|
end
|
154
161
|
end
|
data/lib/httpx/response.rb
CHANGED
@@ -166,10 +166,12 @@ module HTTPX
|
|
166
166
|
decode(Transcoder::Form)
|
167
167
|
end
|
168
168
|
|
169
|
-
# decodes the response payload into a Nokogiri::XML::Node object **if** the payload is valid
|
170
|
-
# "application/xml" (requires the "nokogiri" gem).
|
171
169
|
def xml
|
172
|
-
|
170
|
+
# TODO: remove at next major version.
|
171
|
+
warn "DEPRECATION WARNING: calling `.#{__method__}` on plain HTTPX responses is deprecated. " \
|
172
|
+
"Use HTTPX.plugin(:xml) sessions and call `.#{__method__}` in its responses instead."
|
173
|
+
require "httpx/plugins/xml"
|
174
|
+
decode(Plugins::XML::Transcoder)
|
173
175
|
end
|
174
176
|
|
175
177
|
private
|