httpx 1.5.1 → 1.6.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (94) hide show
  1. checksums.yaml +4 -4
  2. data/doc/release_notes/1_6_0.md +50 -0
  3. data/lib/httpx/adapters/datadog.rb +23 -13
  4. data/lib/httpx/adapters/faraday.rb +14 -9
  5. data/lib/httpx/adapters/webmock.rb +1 -1
  6. data/lib/httpx/callbacks.rb +1 -1
  7. data/lib/httpx/connection/http1.rb +5 -6
  8. data/lib/httpx/connection/http2.rb +30 -12
  9. data/lib/httpx/connection.rb +17 -24
  10. data/lib/httpx/errors.rb +3 -1
  11. data/lib/httpx/io/ssl.rb +1 -4
  12. data/lib/httpx/io/tcp.rb +25 -16
  13. data/lib/httpx/io/unix.rb +4 -3
  14. data/lib/httpx/loggable.rb +4 -1
  15. data/lib/httpx/options.rb +252 -158
  16. data/lib/httpx/plugins/aws_sdk_authentication.rb +2 -0
  17. data/lib/httpx/plugins/aws_sigv4.rb +2 -0
  18. data/lib/httpx/plugins/callbacks.rb +13 -1
  19. data/lib/httpx/plugins/circuit_breaker.rb +2 -0
  20. data/lib/httpx/plugins/content_digest.rb +2 -0
  21. data/lib/httpx/plugins/cookies.rb +2 -2
  22. data/lib/httpx/plugins/digest_auth.rb +2 -0
  23. data/lib/httpx/plugins/expect.rb +2 -0
  24. data/lib/httpx/plugins/fiber_concurrency.rb +195 -0
  25. data/lib/httpx/plugins/follow_redirects.rb +2 -0
  26. data/lib/httpx/plugins/grpc.rb +2 -0
  27. data/lib/httpx/plugins/h2c.rb +26 -16
  28. data/lib/httpx/plugins/internal_telemetry.rb +0 -49
  29. data/lib/httpx/plugins/ntlm_auth.rb +2 -0
  30. data/lib/httpx/plugins/oauth.rb +2 -0
  31. data/lib/httpx/plugins/persistent.rb +27 -18
  32. data/lib/httpx/plugins/proxy/socks4.rb +1 -1
  33. data/lib/httpx/plugins/proxy/socks5.rb +1 -1
  34. data/lib/httpx/plugins/proxy/ssh.rb +2 -0
  35. data/lib/httpx/plugins/proxy.rb +61 -20
  36. data/lib/httpx/plugins/response_cache/file_store.rb +2 -2
  37. data/lib/httpx/plugins/response_cache.rb +2 -0
  38. data/lib/httpx/plugins/retries.rb +2 -0
  39. data/lib/httpx/plugins/ssrf_filter.rb +2 -2
  40. data/lib/httpx/plugins/stream_bidi.rb +3 -3
  41. data/lib/httpx/plugins/upgrade/h2.rb +11 -1
  42. data/lib/httpx/plugins/upgrade.rb +8 -0
  43. data/lib/httpx/pool.rb +15 -10
  44. data/lib/httpx/request/body.rb +8 -3
  45. data/lib/httpx/request.rb +22 -11
  46. data/lib/httpx/resolver/entry.rb +30 -0
  47. data/lib/httpx/resolver/https.rb +3 -1
  48. data/lib/httpx/resolver/multi.rb +5 -2
  49. data/lib/httpx/resolver/native.rb +15 -6
  50. data/lib/httpx/resolver/resolver.rb +3 -5
  51. data/lib/httpx/resolver/system.rb +1 -1
  52. data/lib/httpx/resolver.rb +34 -21
  53. data/lib/httpx/response/body.rb +1 -1
  54. data/lib/httpx/response/buffer.rb +13 -18
  55. data/lib/httpx/selector.rb +92 -34
  56. data/lib/httpx/session.rb +89 -30
  57. data/lib/httpx/session_extensions.rb +3 -2
  58. data/lib/httpx/transcoder/form.rb +1 -13
  59. data/lib/httpx/transcoder/multipart/mime_type_detector.rb +1 -1
  60. data/lib/httpx/transcoder/multipart.rb +14 -0
  61. data/lib/httpx/transcoder/utils/deflater.rb +1 -1
  62. data/lib/httpx/version.rb +1 -1
  63. data/sig/callbacks.rbs +1 -1
  64. data/sig/chainable.rbs +1 -0
  65. data/sig/connection/http1.rbs +2 -0
  66. data/sig/connection/http2.rbs +4 -0
  67. data/sig/connection.rbs +6 -6
  68. data/sig/errors.rbs +3 -1
  69. data/sig/io/ssl.rbs +1 -1
  70. data/sig/io/tcp.rbs +13 -7
  71. data/sig/io/udp.rbs +7 -2
  72. data/sig/io/unix.rbs +0 -1
  73. data/sig/io.rbs +0 -3
  74. data/sig/options.rbs +63 -10
  75. data/sig/plugins/fiber_concurrency.rbs +51 -0
  76. data/sig/plugins/h2c.rbs +5 -1
  77. data/sig/plugins/persistent.rbs +1 -1
  78. data/sig/plugins/proxy/socks4.rbs +1 -1
  79. data/sig/plugins/proxy/socks5.rbs +1 -1
  80. data/sig/plugins/proxy.rbs +5 -2
  81. data/sig/plugins/ssrf_filter.rbs +1 -1
  82. data/sig/plugins/stream_bidi.rbs +2 -2
  83. data/sig/request.rbs +4 -1
  84. data/sig/resolver/entry.rbs +13 -0
  85. data/sig/resolver/native.rbs +1 -0
  86. data/sig/resolver/resolver.rbs +2 -3
  87. data/sig/resolver/system.rbs +2 -2
  88. data/sig/resolver.rbs +10 -11
  89. data/sig/response.rbs +2 -2
  90. data/sig/selector.rbs +18 -10
  91. data/sig/session.rbs +4 -0
  92. data/sig/transcoder/form.rbs +3 -3
  93. data/sig/transcoder/multipart.rbs +9 -3
  94. metadata +9 -3
@@ -42,7 +42,7 @@ module HTTPX
42
42
  %i[join_headers join_trailers join_body].each do |lock_meth|
43
43
  class_eval(<<-METH, __FILE__, __LINE__ + 1)
44
44
  # lock.aware version of +#{lock_meth}+
45
- def #{lock_meth}(*) # def join_headers(*)
45
+ private def #{lock_meth}(*) # private def join_headers(*)
46
46
  return super if @lock.owned?
47
47
 
48
48
  # small race condition between
@@ -119,7 +119,7 @@ module HTTPX
119
119
  class Signal
120
120
  def initialize
121
121
  @closed = false
122
- @pipe_read, @pipe_write = ::IO.pipe
122
+ @pipe_read, @pipe_write = IO.pipe
123
123
  end
124
124
 
125
125
  def state
@@ -127,7 +127,7 @@ module HTTPX
127
127
  end
128
128
 
129
129
  # noop
130
- def log(**); end
130
+ def log(**, &_); end
131
131
 
132
132
  def to_io
133
133
  @pipe_read.to_io
@@ -22,6 +22,16 @@ module HTTPX
22
22
  module ConnectionMethods
23
23
  using URIExtensions
24
24
 
25
+ def interests
26
+ return super unless connecting? && @parser
27
+
28
+ connect
29
+
30
+ return @io.interests if connecting?
31
+
32
+ super
33
+ end
34
+
25
35
  def upgrade_to_h2
26
36
  prev_parser = @parser
27
37
 
@@ -30,7 +40,7 @@ module HTTPX
30
40
  @inflight -= prev_parser.requests.size
31
41
  end
32
42
 
33
- @parser = Connection::HTTP2.new(@write_buffer, @options)
43
+ @parser = @options.http2_class.new(@write_buffer, @options)
34
44
  set_parser_callbacks(@parser)
35
45
  @upgrade_protocol = "h2"
36
46
 
@@ -20,6 +20,8 @@ module HTTPX
20
20
  end
21
21
 
22
22
  module OptionsMethods
23
+ private
24
+
23
25
  def option_upgrade_handlers(value)
24
26
  raise TypeError, ":upgrade_handlers must be a Hash" unless value.is_a?(Hash)
25
27
 
@@ -65,6 +67,12 @@ module HTTPX
65
67
  module ConnectionMethods
66
68
  attr_reader :upgrade_protocol, :hijacked
67
69
 
70
+ def initialize(*)
71
+ super
72
+
73
+ @upgrade_protocol = nil
74
+ end
75
+
68
76
  def hijack_io
69
77
  @hijacked = true
70
78
 
data/lib/httpx/pool.rb CHANGED
@@ -2,6 +2,8 @@
2
2
 
3
3
  require "httpx/selector"
4
4
  require "httpx/connection"
5
+ require "httpx/connection/http2"
6
+ require "httpx/connection/http1"
5
7
  require "httpx/resolver"
6
8
 
7
9
  module HTTPX
@@ -51,18 +53,21 @@ module HTTPX
51
53
  # this takes precedence over per-origin
52
54
  @max_connections_cond.wait(@connection_mtx, @pool_timeout)
53
55
 
54
- acquire_connection(uri, options) || begin
55
- if @connections_counter == @max_connections
56
- # if no matching usable connection was found, the pool will make room and drop a closed connection. if none is found,
57
- # this means that all of them are persistent or being used, so raise a timeout error.
58
- conn = @connections.find { |c| c.state == :closed }
56
+ if (conn = acquire_connection(uri, options))
57
+ return conn
58
+ end
59
+
60
+ if @connections_counter == @max_connections
61
+ # if no matching usable connection was found, the pool will make room and drop a closed connection. if none is found,
62
+ # this means that all of them are persistent or being used, so raise a timeout error.
63
+ conn = @connections.find { |c| c.state == :closed }
59
64
 
60
- raise PoolTimeoutError.new(@pool_timeout,
61
- "Timed out after #{@pool_timeout} seconds while waiting for a connection") unless conn
65
+ raise PoolTimeoutError.new(@pool_timeout,
66
+ "Timed out after #{@pool_timeout} seconds while waiting for a connection") unless conn
62
67
 
63
- drop_connection(conn)
64
- end
68
+ drop_connection(conn)
65
69
  end
70
+
66
71
  end
67
72
 
68
73
  if @origin_counters[uri.origin] == @max_connections_per_origin
@@ -110,7 +115,7 @@ module HTTPX
110
115
 
111
116
  def checkout_resolver(options)
112
117
  resolver_type = options.resolver_class
113
- resolver_type = Resolver.resolver_for(resolver_type)
118
+ resolver_type = Resolver.resolver_for(resolver_type, options)
114
119
 
115
120
  @resolver_mtx.synchronize do
116
121
  resolvers = @resolvers[resolver_type]
@@ -56,7 +56,7 @@ module HTTPX
56
56
  block.call(chunk)
57
57
  end
58
58
  # TODO: use copy_stream once bug is resolved: https://bugs.ruby-lang.org/issues/21131
59
- # ::IO.copy_stream(body, ProcIO.new(block))
59
+ # IO.copy_stream(body, ProcIO.new(block))
60
60
  elsif body.respond_to?(:each)
61
61
  body.each(&block)
62
62
  else
@@ -127,8 +127,13 @@ module HTTPX
127
127
  # @type var body: bodyIO
128
128
  Transcoder::Body.encode(body)
129
129
  elsif (form = params.delete(:form))
130
- # @type var form: Transcoder::urlencoded_input
131
- Transcoder::Form.encode(form)
130
+ if Transcoder::Multipart.multipart?(form)
131
+ # @type var form: Transcoder::multipart_input
132
+ Transcoder::Multipart.encode(form)
133
+ else
134
+ # @type var form: Transcoder::urlencoded_input
135
+ Transcoder::Form.encode(form)
136
+ end
132
137
  elsif (json = params.delete(:json))
133
138
  # @type var body: _ToJson
134
139
  Transcoder::JSON.encode(json)
data/lib/httpx/request.rb CHANGED
@@ -8,6 +8,7 @@ module HTTPX
8
8
  # as well as maintaining the state machine which manages streaming the request onto the wire.
9
9
  class Request
10
10
  extend Forwardable
11
+ include Loggable
11
12
  include Callbacks
12
13
  using URIExtensions
13
14
 
@@ -102,13 +103,16 @@ module HTTPX
102
103
  raise UnsupportedSchemeError, "#{@uri}: #{@uri.scheme}: unsupported URI scheme" unless ALLOWED_URI_SCHEMES.include?(@uri.scheme)
103
104
 
104
105
  @state = :idle
105
- @response = nil
106
- @peer_address = nil
106
+ @response = @peer_address = @context = @informational_status = nil
107
107
  @ping = false
108
108
  @persistent = @options.persistent
109
109
  @active_timeouts = []
110
110
  end
111
111
 
112
+ def complete!(response = @response)
113
+ emit(:complete, response)
114
+ end
115
+
112
116
  # whether request has been buffered with a ping
113
117
  def ping?
114
118
  @ping
@@ -173,17 +177,23 @@ module HTTPX
173
177
  def response=(response)
174
178
  return unless response
175
179
 
176
- if response.is_a?(Response) && response.status < 200
177
- # deal with informational responses
180
+ case response
181
+ when Response
182
+ if response.status < 200
183
+ # deal with informational responses
178
184
 
179
- if response.status == 100 && @headers.key?("expect")
180
- @informational_status = response.status
181
- return
182
- end
185
+ if response.status == 100 && @headers.key?("expect")
186
+ @informational_status = response.status
187
+ return
188
+ end
189
+
190
+ # 103 Early Hints advertises resources in document to browsers.
191
+ # not very relevant for an HTTP client, discard.
192
+ return if response.status >= 103
183
193
 
184
- # 103 Early Hints advertises resources in document to browsers.
185
- # not very relevant for an HTTP client, discard.
186
- return if response.status >= 103
194
+ end
195
+ when ErrorResponse
196
+ response.error.connection = nil if response.error.respond_to?(:connection=)
187
197
  end
188
198
 
189
199
  @response = response
@@ -293,6 +303,7 @@ module HTTPX
293
303
  return if @state == :expect
294
304
 
295
305
  end
306
+ log(level: 3) { "#{@state}] -> #{nextstate}" }
296
307
  @state = nextstate
297
308
  emit(@state, self)
298
309
  nil
@@ -0,0 +1,30 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "ipaddr"
4
+
5
+ module HTTPX
6
+ module Resolver
7
+ class Entry < SimpleDelegator
8
+ attr_reader :address
9
+
10
+ def self.convert(address)
11
+ new(address, rescue_on_convert: true)
12
+ end
13
+
14
+ def initialize(address, expires_in = Float::INFINITY, rescue_on_convert: false)
15
+ @expires_in = expires_in
16
+ @address = address.is_a?(IPAddr) ? address : IPAddr.new(address.to_s)
17
+ super(@address)
18
+ rescue IPAddr::InvalidAddressError
19
+ raise unless rescue_on_convert
20
+
21
+ @address = address.to_s
22
+ super(@address)
23
+ end
24
+
25
+ def expired?
26
+ @expires_in < Utils.now
27
+ end
28
+ end
29
+ end
30
+ end
@@ -59,6 +59,8 @@ module HTTPX
59
59
  resolve(connection)
60
60
  end
61
61
 
62
+ # This is already indirectly monitored bt the HTTP connection. In order to skip
63
+ # monitoring, this method returns <tt>true</tt>.
62
64
  def closed?
63
65
  true
64
66
  end
@@ -202,7 +204,7 @@ module HTTPX
202
204
  @queries.delete_if { |_, conn| connection == conn }
203
205
 
204
206
  Resolver.cached_lookup_set(hostname, @family, addresses) if @resolver_options[:cache]
205
- catch(:coalesced) { emit_addresses(connection, @family, addresses.map { |addr| addr["data"] }) }
207
+ catch(:coalesced) { emit_addresses(connection, @family, addresses.map { |a| Resolver::Entry.new(a["data"], a["TTL"]) }) }
206
208
  end
207
209
  end
208
210
  return if @connections.empty?
@@ -11,8 +11,7 @@ module HTTPX
11
11
  attr_reader :resolvers, :options
12
12
 
13
13
  def initialize(resolver_type, options)
14
- @current_selector = nil
15
- @current_session = nil
14
+ @current_selector = @current_session = nil
16
15
  @options = options
17
16
  @resolver_options = @options.resolver_options
18
17
 
@@ -35,6 +34,10 @@ module HTTPX
35
34
  @resolvers.each { |r| r.__send__(__method__, s) }
36
35
  end
37
36
 
37
+ def log(*args, **kwargs, &blk)
38
+ @resolvers.each { |r| r.__send__(__method__, *args, **kwargs, &blk) }
39
+ end
40
+
38
41
  def closed?
39
42
  @resolvers.all?(&:closed?)
40
43
  end
@@ -42,6 +42,7 @@ module HTTPX
42
42
  @read_buffer = "".b
43
43
  @write_buffer = Buffer.new(@resolver_options[:packet_size])
44
44
  @state = :idle
45
+ @timer = nil
45
46
  end
46
47
 
47
48
  def close
@@ -104,11 +105,11 @@ module HTTPX
104
105
  private
105
106
 
106
107
  def calculate_interests
107
- return :w unless @write_buffer.empty?
108
+ return if @queries.empty?
108
109
 
109
- return :r unless @queries.empty?
110
+ return :r if @write_buffer.empty?
110
111
 
111
- nil
112
+ :w
112
113
  end
113
114
 
114
115
  def consume
@@ -153,6 +154,8 @@ module HTTPX
153
154
  @timer = @current_selector.after(timeout) do
154
155
  next unless @connections.include?(connection)
155
156
 
157
+ @timer = nil
158
+
156
159
  do_retry(h, connection, timeout)
157
160
  end
158
161
  end
@@ -270,6 +273,8 @@ module HTTPX
270
273
  def parse(buffer)
271
274
  @timer.cancel
272
275
 
276
+ @timer = nil
277
+
273
278
  code, result = Resolver.decode_dns_answer(buffer)
274
279
 
275
280
  case code
@@ -370,7 +375,9 @@ module HTTPX
370
375
  @timeouts.delete(connection.peer.host)
371
376
  @connections.delete(connection)
372
377
  Resolver.cached_lookup_set(connection.peer.host, @family, addresses) if @resolver_options[:cache]
373
- catch(:coalesced) { emit_addresses(connection, @family, addresses.map { |addr| addr["data"] }) }
378
+ catch(:coalesced) do
379
+ emit_addresses(connection, @family, addresses.map { |a| Resolver::Entry.new(a["data"], a["TTL"]) })
380
+ end
374
381
  end
375
382
  end
376
383
  close_or_resolve
@@ -383,7 +390,8 @@ module HTTPX
383
390
 
384
391
  raise Error, "no URI to resolve" unless connection
385
392
 
386
- return unless @write_buffer.empty?
393
+ # do not buffer query if previous is still in the buffer or awaiting reply/retry
394
+ return unless @write_buffer.empty? && @timer.nil?
387
395
 
388
396
  hostname ||= @queries.key(connection)
389
397
 
@@ -444,7 +452,7 @@ module HTTPX
444
452
  when :tcp
445
453
  log { "resolver #{FAMILY_TYPES[@record_type]}: server: tcp://#{ip}:#{port}..." }
446
454
  origin = URI("tcp://#{ip}:#{port}")
447
- TCP.new(origin, [ip], @options)
455
+ TCP.new(origin, [Resolver::Entry.new(ip)], @options)
448
456
  end
449
457
  end
450
458
 
@@ -480,6 +488,7 @@ module HTTPX
480
488
  @write_buffer.clear
481
489
  @read_buffer.clear
482
490
  end
491
+ log(level: 3) { "#{@state} -> #{nextstate}" }
483
492
  @state = nextstate
484
493
  rescue Errno::ECONNREFUSED,
485
494
  Errno::EADDRNOTAVAIL,
@@ -1,7 +1,6 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  require "resolv"
4
- require "ipaddr"
5
4
 
6
5
  module HTTPX
7
6
  # Base class for all internal internet name resolvers. It handles basic blocks
@@ -69,12 +68,11 @@ module HTTPX
69
68
  end
70
69
 
71
70
  def emit_addresses(connection, family, addresses, early_resolve = false)
72
- addresses.map! do |address|
73
- address.is_a?(IPAddr) ? address : IPAddr.new(address.to_s)
74
- end
71
+ addresses.map! { |address| address.is_a?(Resolver::Entry) ? address : Resolver::Entry.new(address) }
75
72
 
76
73
  # double emission check, but allow early resolution to work
77
- return if !early_resolve && connection.addresses && !addresses.intersect?(connection.addresses)
74
+ conn_addrs = connection.addresses
75
+ return if !early_resolve && conn_addrs && (!conn_addrs.empty? && !addresses.intersect?(!conn_addrs))
78
76
 
79
77
  log do
80
78
  "resolver #{FAMILY_TYPES[RECORD_TYPES[family]]}: " \
@@ -127,7 +127,7 @@ module HTTPX
127
127
  when :open
128
128
  return unless @state == :idle
129
129
 
130
- @pipe_read, @pipe_write = ::IO.pipe
130
+ @pipe_read, @pipe_write = IO.pipe
131
131
  when :closed
132
132
  return unless @state == :open
133
133
 
@@ -1,12 +1,12 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  require "resolv"
4
- require "ipaddr"
5
4
 
6
5
  module HTTPX
7
6
  module Resolver
8
7
  RESOLVE_TIMEOUT = [2, 3].freeze
9
8
 
9
+ require "httpx/resolver/entry"
10
10
  require "httpx/resolver/resolver"
11
11
  require "httpx/resolver/system"
12
12
  require "httpx/resolver/native"
@@ -18,36 +18,40 @@ module HTTPX
18
18
 
19
19
  @identifier_mutex = Thread::Mutex.new
20
20
  @identifier = 1
21
- @system_resolver = Resolv::Hosts.new
21
+ @hosts_resolver = Resolv::Hosts.new
22
22
 
23
23
  module_function
24
24
 
25
- def resolver_for(resolver_type)
25
+ def resolver_for(resolver_type, options)
26
26
  case resolver_type
27
- when :native then Native
28
- when :system then System
29
- when :https then HTTPS
30
- else
31
- return resolver_type if resolver_type.is_a?(Class) && resolver_type < Resolver
27
+ when Symbol
28
+ meth = :"resolver_#{resolver_type}_class"
32
29
 
33
- raise Error, "unsupported resolver type (#{resolver_type})"
30
+ return options.__send__(meth) if options.respond_to?(meth)
31
+ when Class
32
+ return resolver_type if resolver_type < Resolver
34
33
  end
34
+
35
+ raise Error, "unsupported resolver type (#{resolver_type})"
35
36
  end
36
37
 
37
38
  def nolookup_resolve(hostname)
38
- ip_resolve(hostname) || cached_lookup(hostname) || system_resolve(hostname)
39
+ ip_resolve(hostname) || cached_lookup(hostname) || hosts_resolve(hostname)
39
40
  end
40
41
 
42
+ # tries to convert +hostname+ into an IPAddr, returns <tt>nil</tt> otherwise.
41
43
  def ip_resolve(hostname)
42
- [IPAddr.new(hostname)]
44
+ [Entry.new(hostname)]
43
45
  rescue ArgumentError
44
46
  end
45
47
 
46
- def system_resolve(hostname)
47
- ips = @system_resolver.getaddresses(hostname)
48
+ # matches +hostname+ to entries in the hosts file, returns <tt>nil</nil> if none is
49
+ # found, or there is no hosts file.
50
+ def hosts_resolve(hostname)
51
+ ips = @hosts_resolver.getaddresses(hostname)
48
52
  return if ips.empty?
49
53
 
50
- ips.map { |ip| IPAddr.new(ip) }
54
+ ips.map { |ip| Entry.new(ip) }
51
55
  rescue IOError
52
56
  end
53
57
 
@@ -59,10 +63,6 @@ module HTTPX
59
63
  end
60
64
 
61
65
  def cached_lookup_set(hostname, family, entries)
62
- now = Utils.now
63
- entries.each do |entry|
64
- entry["TTL"] += now
65
- end
66
66
  lookup_synchronize do |lookups|
67
67
  case family
68
68
  when Socket::AF_INET6
@@ -83,6 +83,18 @@ module HTTPX
83
83
  end
84
84
  end
85
85
 
86
+ def cached_lookup_evict(hostname, ip)
87
+ ip = ip.to_s
88
+
89
+ lookup_synchronize do |lookups|
90
+ entries = lookups[hostname]
91
+
92
+ return unless entries
93
+
94
+ lookups.delete_if { |entry| entry["data"] == ip }
95
+ end
96
+ end
97
+
86
98
  # do not use directly!
87
99
  def lookup(hostname, lookups, ttl)
88
100
  return unless lookups.key?(hostname)
@@ -95,7 +107,7 @@ module HTTPX
95
107
  if (als = address["alias"])
96
108
  lookup(als, lookups, ttl)
97
109
  else
98
- IPAddr.new(address["data"])
110
+ Entry.new(address["data"], address["TTL"])
99
111
  end
100
112
  end.compact
101
113
 
@@ -129,19 +141,20 @@ module HTTPX
129
141
 
130
142
  addresses = []
131
143
 
144
+ now = Utils.now
132
145
  message.each_answer do |question, _, value|
133
146
  case value
134
147
  when Resolv::DNS::Resource::IN::CNAME
135
148
  addresses << {
136
149
  "name" => question.to_s,
137
- "TTL" => value.ttl,
150
+ "TTL" => (now + value.ttl),
138
151
  "alias" => value.name.to_s,
139
152
  }
140
153
  when Resolv::DNS::Resource::IN::A,
141
154
  Resolv::DNS::Resource::IN::AAAA
142
155
  addresses << {
143
156
  "name" => question.to_s,
144
- "TTL" => value.ttl,
157
+ "TTL" => (now + value.ttl),
145
158
  "data" => value.address.to_s,
146
159
  }
147
160
  end
@@ -136,7 +136,7 @@ module HTTPX
136
136
  if dest.respond_to?(:path) && @buffer.respond_to?(:path)
137
137
  FileUtils.mv(@buffer.path, dest.path)
138
138
  else
139
- ::IO.copy_stream(@buffer, dest)
139
+ IO.copy_stream(@buffer, dest)
140
140
  end
141
141
  end
142
142
 
@@ -29,7 +29,9 @@ module HTTPX
29
29
  when StringIO
30
30
  StringIO.new(other.buffer.string, mode: File::RDONLY)
31
31
  else
32
- other.buffer.class.new(other.buffer.path, encoding: Encoding::BINARY, mode: File::RDONLY)
32
+ other.buffer.class.new(other.buffer.path, encoding: Encoding::BINARY, mode: File::RDONLY).tap do |temp|
33
+ FileUtils.copy_file(other.buffer.path, temp.path)
34
+ end
33
35
  end
34
36
  end
35
37
 
@@ -75,22 +77,15 @@ module HTTPX
75
77
  super || begin
76
78
  return false unless other.is_a?(Response::Buffer)
77
79
 
78
- if @buffer.nil?
79
- other.buffer.nil?
80
- elsif @buffer.respond_to?(:read) &&
81
- other.respond_to?(:read)
82
- buffer_pos = @buffer.pos
83
- other_pos = other.buffer.pos
84
- @buffer.rewind
85
- other.buffer.rewind
86
- begin
87
- FileUtils.compare_stream(@buffer, other.buffer)
88
- ensure
89
- @buffer.pos = buffer_pos
90
- other.buffer.pos = other_pos
91
- end
92
- else
93
- to_s == other.to_s
80
+ buffer_pos = @buffer.pos
81
+ other_pos = other.buffer.pos
82
+ @buffer.rewind
83
+ other.buffer.rewind
84
+ begin
85
+ FileUtils.compare_stream(@buffer, other.buffer)
86
+ ensure
87
+ @buffer.pos = buffer_pos
88
+ other.buffer.pos = other_pos
94
89
  end
95
90
  end
96
91
  end
@@ -110,7 +105,7 @@ module HTTPX
110
105
 
111
106
  if aux
112
107
  aux.rewind
113
- ::IO.copy_stream(aux, @buffer)
108
+ IO.copy_stream(aux, @buffer)
114
109
  aux.close
115
110
  end
116
111