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.
Files changed (89) hide show
  1. checksums.yaml +4 -4
  2. data/doc/release_notes/1_4_0.md +43 -0
  3. data/doc/release_notes/1_4_1.md +19 -0
  4. data/lib/httpx/adapters/datadog.rb +55 -83
  5. data/lib/httpx/adapters/faraday.rb +2 -0
  6. data/lib/httpx/adapters/webmock.rb +18 -6
  7. data/lib/httpx/callbacks.rb +0 -5
  8. data/lib/httpx/chainable.rb +3 -1
  9. data/lib/httpx/connection/http2.rb +12 -8
  10. data/lib/httpx/connection.rb +192 -22
  11. data/lib/httpx/errors.rb +12 -0
  12. data/lib/httpx/loggable.rb +5 -5
  13. data/lib/httpx/options.rb +26 -16
  14. data/lib/httpx/plugins/aws_sigv4.rb +31 -16
  15. data/lib/httpx/plugins/callbacks.rb +12 -2
  16. data/lib/httpx/plugins/circuit_breaker.rb +0 -5
  17. data/lib/httpx/plugins/content_digest.rb +202 -0
  18. data/lib/httpx/plugins/expect.rb +4 -3
  19. data/lib/httpx/plugins/follow_redirects.rb +7 -8
  20. data/lib/httpx/plugins/grpc/grpc_encoding.rb +2 -0
  21. data/lib/httpx/plugins/h2c.rb +23 -20
  22. data/lib/httpx/plugins/internal_telemetry.rb +27 -0
  23. data/lib/httpx/plugins/persistent.rb +16 -0
  24. data/lib/httpx/plugins/proxy/http.rb +17 -19
  25. data/lib/httpx/plugins/proxy.rb +91 -93
  26. data/lib/httpx/plugins/retries.rb +5 -8
  27. data/lib/httpx/plugins/upgrade.rb +5 -10
  28. data/lib/httpx/plugins/webdav.rb +6 -0
  29. data/lib/httpx/plugins/xml.rb +76 -0
  30. data/lib/httpx/pool.rb +73 -244
  31. data/lib/httpx/request/body.rb +25 -26
  32. data/lib/httpx/request.rb +7 -1
  33. data/lib/httpx/resolver/https.rb +15 -20
  34. data/lib/httpx/resolver/multi.rb +34 -16
  35. data/lib/httpx/resolver/native.rb +66 -25
  36. data/lib/httpx/resolver/resolver.rb +59 -15
  37. data/lib/httpx/resolver/system.rb +31 -15
  38. data/lib/httpx/resolver.rb +21 -14
  39. data/lib/httpx/response.rb +5 -3
  40. data/lib/httpx/selector.rb +160 -95
  41. data/lib/httpx/session.rb +273 -140
  42. data/lib/httpx/transcoder/body.rb +15 -31
  43. data/lib/httpx/transcoder/gzip.rb +0 -3
  44. data/lib/httpx/transcoder/json.rb +14 -2
  45. data/lib/httpx/transcoder/multipart/part.rb +1 -1
  46. data/lib/httpx/transcoder/utils/deflater.rb +7 -4
  47. data/lib/httpx/transcoder/utils/inflater.rb +2 -0
  48. data/lib/httpx/transcoder.rb +0 -1
  49. data/lib/httpx/version.rb +1 -1
  50. data/lib/httpx.rb +20 -21
  51. data/sig/callbacks.rbs +0 -1
  52. data/sig/chainable.rbs +4 -0
  53. data/sig/connection/http2.rbs +1 -1
  54. data/sig/connection.rbs +29 -3
  55. data/sig/errors.rbs +6 -0
  56. data/sig/loggable.rbs +2 -0
  57. data/sig/options.rbs +7 -0
  58. data/sig/plugins/aws_sigv4.rbs +8 -2
  59. data/sig/plugins/content_digest.rbs +51 -0
  60. data/sig/plugins/cookies/cookie.rbs +9 -0
  61. data/sig/plugins/grpc/call.rbs +4 -0
  62. data/sig/plugins/persistent.rbs +4 -1
  63. data/sig/plugins/proxy/socks5.rbs +11 -3
  64. data/sig/plugins/proxy.rbs +18 -11
  65. data/sig/plugins/push_promise.rbs +3 -0
  66. data/sig/plugins/rate_limiter.rbs +2 -0
  67. data/sig/plugins/retries.rbs +1 -1
  68. data/sig/plugins/ssrf_filter.rbs +26 -0
  69. data/sig/plugins/webdav.rbs +23 -0
  70. data/sig/plugins/xml.rbs +37 -0
  71. data/sig/pool.rbs +25 -33
  72. data/sig/request/body.rbs +5 -9
  73. data/sig/resolver/multi.rbs +26 -1
  74. data/sig/resolver/native.rbs +2 -2
  75. data/sig/resolver/resolver.rbs +21 -2
  76. data/sig/resolver.rbs +5 -1
  77. data/sig/response/buffer.rbs +1 -1
  78. data/sig/selector.rbs +30 -4
  79. data/sig/session.rbs +47 -18
  80. data/sig/transcoder/body.rbs +2 -4
  81. data/sig/transcoder/chunker.rbs +1 -1
  82. data/sig/transcoder/deflate.rbs +1 -0
  83. data/sig/transcoder/form.rbs +8 -0
  84. data/sig/transcoder/gzip.rbs +4 -1
  85. data/sig/transcoder/utils/body_reader.rbs +3 -3
  86. data/sig/transcoder/utils/deflater.rbs +3 -3
  87. metadata +12 -4
  88. data/lib/httpx/transcoder/xml.rb +0 -52
  89. 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 { "resolver: failed resolving on nameserver #{@nameserver[@ns_index - 1]} (#{e.message})" }
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.origin.host
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 { "resolver: timeout after #{timeout}s, retry(#{@timeouts[host].first}) #{host}..." }
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 { "resolver: failed resolving #{host} on nameserver #{@nameserver[@ns_index - 1]} (timeout error)" }
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
- raise ResolveTimeoutError.new(loop_time, "Timed out while resolving #{connection.origin.host}")
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
- unless @queries.value?(connection)
261
+ if @queries.value?(connection)
262
+ resolve
263
+ else
250
264
  @connections.delete(connection)
251
- raise NativeResolveError.new(connection, connection.origin.host, "name or service not known")
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.origin.host, "unknown DNS error (error code #{result})")
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.origin.host, result.message)
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.origin.host)
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.origin.host == name
327
+ @timeouts.delete(name) unless connection.peer.host == name
313
328
 
314
- if catch(:coalesced) { early_resolve(connection, hostname: hostname_alias) }
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.origin.host)
344
+ @timeouts.delete(connection.peer.host)
330
345
  @connections.delete(connection)
331
- Resolver.cached_lookup_set(connection.origin.host, @family, addresses) if @resolver_options[:cache]
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
- return emit(:close) if @connections.empty?
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.origin.host
349
- log { "resolver: resolve IDN #{connection.origin.non_ascii_hostname} as #{hostname}" } if connection.origin.non_ascii_hostname
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: query #{@record_type.name.split("::").last} for #{hostname}" }
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 :pool
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 { "resolver: answer #{FAMILY_TYPES[RECORD_TYPES[family]]} #{connection.origin.host}: #{addresses.inspect}" }
60
- if @pool && # if triggered by early resolve, pool may not be here yet
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.origin.host.to_s
65
- log { "resolver: A response, applying resolution delay..." }
66
- @pool.after(0.05) do
67
- unless connection.state == :closed ||
68
- # double emission check
69
- (connection.addresses && addresses.intersect?(connection.addresses))
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.origin.host)
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.origin.host, ex = nil)
108
- emit(:error, connection, resolve_error(hostname, ex))
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 connections
51
- EMPTY
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.origin.host].first
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
- while @pipe_read.ready? && (event = @pipe_read.getbyte)
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
- family, connection = pair
136
- emit_resolve_error(connection, connection.origin.host, error)
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.origin.host
159
+ hostname = connection.peer.host
152
160
  scheme = connection.origin.scheme
153
- log { "resolver: resolve IDN #{connection.origin.non_ascii_hostname} as #{hostname}" } if connection.origin.non_ascii_hostname
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.origin.host]
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
@@ -53,8 +53,8 @@ module HTTPX
53
53
 
54
54
  def cached_lookup(hostname)
55
55
  now = Utils.now
56
- @lookup_mutex.synchronize do
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
- @lookup_mutex.synchronize do
66
+ lookup_synchronize do |lookups|
67
67
  case family
68
68
  when Socket::AF_INET6
69
- @lookups[hostname].concat(entries)
69
+ lookups[hostname].concat(entries)
70
70
  when Socket::AF_INET
71
- @lookups[hostname].unshift(*entries)
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
- @lookups[entry["name"]] << entry
78
+ lookups[entry["name"]] << entry
79
79
  when Socket::AF_INET
80
- @lookups[entry["name"]].unshift(entry)
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 @lookups.key?(hostname)
87
+ def lookup(hostname, lookups, ttl)
88
+ return unless lookups.key?(hostname)
89
89
 
90
- entries = @lookups[hostname] = @lookups[hostname].select do |address|
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
- @identifier_mutex.synchronize { @identifier = (@identifier + 1) & 0xFFFF }
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
@@ -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
- decode(Transcoder::Xml)
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