httpx 0.9.0 → 0.11.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 (120) hide show
  1. checksums.yaml +4 -4
  2. data/LICENSE.txt +48 -0
  3. data/README.md +13 -3
  4. data/doc/release_notes/0_10_0.md +66 -0
  5. data/doc/release_notes/0_10_1.md +37 -0
  6. data/doc/release_notes/0_10_2.md +5 -0
  7. data/doc/release_notes/0_11_0.md +76 -0
  8. data/doc/release_notes/0_11_1.md +1 -0
  9. data/lib/httpx.rb +2 -0
  10. data/lib/httpx/adapters/datadog.rb +205 -0
  11. data/lib/httpx/adapters/faraday.rb +1 -3
  12. data/lib/httpx/adapters/webmock.rb +123 -0
  13. data/lib/httpx/chainable.rb +10 -9
  14. data/lib/httpx/connection.rb +7 -24
  15. data/lib/httpx/connection/http1.rb +15 -2
  16. data/lib/httpx/connection/http2.rb +15 -16
  17. data/lib/httpx/domain_name.rb +438 -0
  18. data/lib/httpx/errors.rb +4 -1
  19. data/lib/httpx/extensions.rb +21 -1
  20. data/lib/httpx/headers.rb +1 -0
  21. data/lib/httpx/io/ssl.rb +4 -9
  22. data/lib/httpx/io/tcp.rb +6 -5
  23. data/lib/httpx/io/udp.rb +8 -4
  24. data/lib/httpx/options.rb +2 -0
  25. data/lib/httpx/parser/http1.rb +14 -17
  26. data/lib/httpx/plugins/compression.rb +28 -63
  27. data/lib/httpx/plugins/compression/brotli.rb +10 -14
  28. data/lib/httpx/plugins/compression/deflate.rb +7 -6
  29. data/lib/httpx/plugins/compression/gzip.rb +23 -5
  30. data/lib/httpx/plugins/cookies.rb +21 -60
  31. data/lib/httpx/plugins/cookies/cookie.rb +173 -0
  32. data/lib/httpx/plugins/cookies/jar.rb +74 -0
  33. data/lib/httpx/plugins/cookies/set_cookie_parser.rb +142 -0
  34. data/lib/httpx/plugins/expect.rb +34 -11
  35. data/lib/httpx/plugins/follow_redirects.rb +20 -2
  36. data/lib/httpx/plugins/h2c.rb +1 -1
  37. data/lib/httpx/plugins/multipart.rb +41 -30
  38. data/lib/httpx/plugins/multipart/encoder.rb +115 -0
  39. data/lib/httpx/plugins/multipart/mime_type_detector.rb +64 -0
  40. data/lib/httpx/plugins/multipart/part.rb +34 -0
  41. data/lib/httpx/plugins/persistent.rb +6 -1
  42. data/lib/httpx/plugins/proxy.rb +16 -2
  43. data/lib/httpx/plugins/proxy/socks4.rb +14 -14
  44. data/lib/httpx/plugins/proxy/socks5.rb +3 -2
  45. data/lib/httpx/plugins/push_promise.rb +2 -2
  46. data/lib/httpx/plugins/rate_limiter.rb +51 -0
  47. data/lib/httpx/plugins/retries.rb +3 -2
  48. data/lib/httpx/plugins/stream.rb +109 -13
  49. data/lib/httpx/pool.rb +14 -20
  50. data/lib/httpx/request.rb +29 -31
  51. data/lib/httpx/resolver.rb +7 -6
  52. data/lib/httpx/resolver/https.rb +25 -25
  53. data/lib/httpx/resolver/native.rb +29 -22
  54. data/lib/httpx/resolver/resolver_mixin.rb +4 -2
  55. data/lib/httpx/resolver/system.rb +3 -3
  56. data/lib/httpx/response.rb +16 -23
  57. data/lib/httpx/selector.rb +11 -17
  58. data/lib/httpx/session.rb +39 -30
  59. data/lib/httpx/transcoder.rb +20 -0
  60. data/lib/httpx/transcoder/chunker.rb +0 -2
  61. data/lib/httpx/transcoder/form.rb +9 -7
  62. data/lib/httpx/transcoder/json.rb +0 -4
  63. data/lib/httpx/utils.rb +45 -0
  64. data/lib/httpx/version.rb +1 -1
  65. data/sig/buffer.rbs +24 -0
  66. data/sig/callbacks.rbs +14 -0
  67. data/sig/chainable.rbs +37 -0
  68. data/sig/connection.rbs +85 -0
  69. data/sig/connection/http1.rbs +66 -0
  70. data/sig/connection/http2.rbs +77 -0
  71. data/sig/domain_name.rbs +17 -0
  72. data/sig/errors.rbs +3 -0
  73. data/sig/headers.rbs +45 -0
  74. data/sig/httpx.rbs +15 -0
  75. data/sig/loggable.rbs +11 -0
  76. data/sig/options.rbs +118 -0
  77. data/sig/parser/http1.rbs +50 -0
  78. data/sig/plugins/authentication.rbs +11 -0
  79. data/sig/plugins/basic_authentication.rbs +13 -0
  80. data/sig/plugins/compression.rbs +55 -0
  81. data/sig/plugins/compression/brotli.rbs +21 -0
  82. data/sig/plugins/compression/deflate.rbs +17 -0
  83. data/sig/plugins/compression/gzip.rbs +29 -0
  84. data/sig/plugins/cookies.rbs +26 -0
  85. data/sig/plugins/cookies/cookie.rbs +50 -0
  86. data/sig/plugins/cookies/jar.rbs +27 -0
  87. data/sig/plugins/digest_authentication.rbs +33 -0
  88. data/sig/plugins/expect.rbs +19 -0
  89. data/sig/plugins/follow_redirects.rbs +37 -0
  90. data/sig/plugins/h2c.rbs +26 -0
  91. data/sig/plugins/multipart.rbs +44 -0
  92. data/sig/plugins/persistent.rbs +17 -0
  93. data/sig/plugins/proxy.rbs +47 -0
  94. data/sig/plugins/proxy/http.rbs +14 -0
  95. data/sig/plugins/proxy/socks4.rbs +33 -0
  96. data/sig/plugins/proxy/socks5.rbs +36 -0
  97. data/sig/plugins/proxy/ssh.rbs +18 -0
  98. data/sig/plugins/push_promise.rbs +22 -0
  99. data/sig/plugins/rate_limiter.rbs +11 -0
  100. data/sig/plugins/retries.rbs +48 -0
  101. data/sig/plugins/stream.rbs +39 -0
  102. data/sig/pool.rbs +36 -0
  103. data/sig/registry.rbs +9 -0
  104. data/sig/request.rbs +61 -0
  105. data/sig/resolver.rbs +26 -0
  106. data/sig/resolver/https.rbs +51 -0
  107. data/sig/resolver/native.rbs +60 -0
  108. data/sig/resolver/resolver_mixin.rbs +27 -0
  109. data/sig/resolver/system.rbs +17 -0
  110. data/sig/response.rbs +87 -0
  111. data/sig/selector.rbs +20 -0
  112. data/sig/session.rbs +49 -0
  113. data/sig/timeout.rbs +29 -0
  114. data/sig/transcoder.rbs +18 -0
  115. data/sig/transcoder/body.rbs +20 -0
  116. data/sig/transcoder/chunker.rbs +32 -0
  117. data/sig/transcoder/form.rbs +22 -0
  118. data/sig/transcoder/json.rbs +16 -0
  119. metadata +99 -59
  120. data/lib/httpx/resolver/options.rb +0 -25
@@ -33,11 +33,7 @@ module HTTPX
33
33
 
34
34
  USER_AGENT = "httpx.rb/#{VERSION}"
35
35
 
36
- attr_reader :verb, :uri, :headers, :body, :state
37
-
38
- attr_reader :options, :response
39
-
40
- def_delegator :@body, :<<
36
+ attr_reader :verb, :uri, :headers, :body, :state, :options, :response
41
37
 
42
38
  def_delegator :@body, :empty?
43
39
 
@@ -45,7 +41,7 @@ module HTTPX
45
41
 
46
42
  def initialize(verb, uri, options = {})
47
43
  @verb = verb.to_s.downcase.to_sym
48
- @uri = URI(uri.to_s)
44
+ @uri = Utils.uri(uri)
49
45
  @options = Options.new(options)
50
46
 
51
47
  raise(Error, "unknown method: #{verb}") unless METHODS.include?(@verb)
@@ -64,17 +60,15 @@ module HTTPX
64
60
  :w
65
61
  end
66
62
 
67
- # :nocov:
68
63
  if RUBY_VERSION < "2.2"
69
- # rubocop: disable Lint/UriEscapeUnescape:
64
+ URIParser = URI::DEFAULT_PARSER
65
+
70
66
  def initialize_with_escape(verb, uri, options = {})
71
- initialize_without_escape(verb, URI.escape(uri.to_s), options)
67
+ initialize_without_escape(verb, URIParser.escape(uri.to_s), options)
72
68
  end
73
69
  alias_method :initialize_without_escape, :initialize
74
70
  alias_method :initialize, :initialize_with_escape
75
- # rubocop: enable Lint/UriEscapeUnescape:
76
71
  end
77
- # :nocov:
78
72
 
79
73
  def merge_headers(h)
80
74
  @headers = @headers.merge(h)
@@ -87,6 +81,10 @@ module HTTPX
87
81
  def response=(response)
88
82
  return unless response
89
83
 
84
+ if response.status == 100
85
+ @informational_status = response.status
86
+ return
87
+ end
90
88
  @response = response
91
89
  end
92
90
 
@@ -112,7 +110,7 @@ module HTTPX
112
110
 
113
111
  query = []
114
112
  if (q = @options.params)
115
- query << URI.encode_www_form(q)
113
+ query << Transcoder.registry("form").encode(q)
116
114
  end
117
115
  query << @uri.query if @uri.query
118
116
  @query = query.join("&")
@@ -176,23 +174,23 @@ module HTTPX
176
174
  end
177
175
  end
178
176
 
177
+ def rewind
178
+ return if empty?
179
+
180
+ @body.rewind if @body.respond_to?(:rewind)
181
+ end
182
+
179
183
  def empty?
180
184
  return true if @body.nil?
181
185
  return false if chunked?
182
186
 
183
- bytesize.zero?
187
+ @body.bytesize.zero?
184
188
  end
185
189
 
186
190
  def bytesize
187
191
  return 0 if @body.nil?
188
192
 
189
- if @body.respond_to?(:bytesize)
190
- @body.bytesize
191
- elsif @body.respond_to?(:size)
192
- @body.size
193
- else
194
- raise Error, "cannot determine size of body: #{@body.inspect}"
195
- end
193
+ @body.bytesize
196
194
  end
197
195
 
198
196
  def stream(body)
@@ -224,6 +222,7 @@ module HTTPX
224
222
  def transition(nextstate)
225
223
  case nextstate
226
224
  when :idle
225
+ @body.rewind
227
226
  @response = nil
228
227
  @drainer = nil
229
228
  when :headers
@@ -233,15 +232,15 @@ module HTTPX
233
232
  @state == :expect
234
233
 
235
234
  if @headers.key?("expect")
236
- unless @response
237
- @state = :expect
238
- return
239
- end
240
-
241
- case @response.status
242
- when 100
243
- # deallocate
244
- @response = nil
235
+ if @informational_status && @informational_status == 100
236
+ # check for 100 Continue response, and deallocate the var
237
+ # if @informational_status == 100
238
+ # @response = nil
239
+ # end
240
+ else
241
+ return if @state == :expect # do not re-set it
242
+
243
+ nextstate = :expect
245
244
  end
246
245
  end
247
246
  when :done
@@ -253,8 +252,7 @@ module HTTPX
253
252
  end
254
253
 
255
254
  def expects?
256
- @headers["expect"] == "100-continue" &&
257
- @response && @response.status == 100
255
+ @headers["expect"] == "100-continue" && @informational_status == 100 && !@response
258
256
  end
259
257
 
260
258
  class ProcIO
@@ -1,15 +1,18 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  require "resolv"
4
- require "httpx/resolver/resolver_mixin"
5
- require "httpx/resolver/system"
6
- require "httpx/resolver/native"
7
- require "httpx/resolver/https"
8
4
 
9
5
  module HTTPX
10
6
  module Resolver
11
7
  extend Registry
12
8
 
9
+ RESOLVE_TIMEOUT = 5
10
+
11
+ require "httpx/resolver/resolver_mixin"
12
+ require "httpx/resolver/system"
13
+ require "httpx/resolver/native"
14
+ require "httpx/resolver/https"
15
+
13
16
  register :system, System
14
17
  register :native, Native
15
18
  register :https, HTTPS
@@ -101,5 +104,3 @@ module HTTPX
101
104
  end
102
105
  end
103
106
  end
104
-
105
- require "httpx/resolver/options"
@@ -21,6 +21,7 @@ module HTTPX
21
21
  DEFAULTS = {
22
22
  uri: NAMESERVER,
23
23
  use_get: false,
24
+ record_types: RECORD_TYPES.keys,
24
25
  }.freeze
25
26
 
26
27
  def_delegator :@connections, :empty?
@@ -29,27 +30,29 @@ module HTTPX
29
30
 
30
31
  def initialize(options)
31
32
  @options = Options.new(options)
32
- @resolver_options = Resolver::Options.new(DEFAULTS.merge(@options.resolver_options || {}))
33
- @_record_types = Hash.new { |types, host| types[host] = RECORD_TYPES.keys.dup }
33
+ @resolver_options = DEFAULTS.merge(@options.resolver_options)
34
+ @_record_types = Hash.new { |types, host| types[host] = @resolver_options[:record_types].dup }
34
35
  @queries = {}
35
36
  @requests = {}
36
37
  @connections = []
37
- @uri = URI(@resolver_options.uri)
38
+ @uri = URI(@resolver_options[:uri])
38
39
  @uri_addresses = nil
40
+ @resolver = Resolv::DNS.new
41
+ @resolver.timeouts = @resolver_options.fetch(:timeouts, Resolver::RESOLVE_TIMEOUT)
39
42
  end
40
43
 
41
44
  def <<(connection)
42
45
  return if @uri.origin == connection.origin.to_s
43
46
 
44
- @uri_addresses ||= Resolv.getaddresses(@uri.host)
47
+ @uri_addresses ||= ip_resolve(@uri.host) || system_resolve(@uri.host) || @resolver.getaddresses(@uri.host)
45
48
 
46
49
  if @uri_addresses.empty?
47
- ex = ResolveError.new("Can't resolve #{connection.origin.host}")
50
+ ex = ResolveError.new("Can't resolve DNS server #{@uri.host}")
48
51
  ex.set_backtrace(caller)
49
- emit(:error, connection, ex)
50
- else
51
- early_resolve(connection) || resolve(connection)
52
+ throw(:resolve_error, ex)
52
53
  end
54
+
55
+ early_resolve(connection) || resolve(connection)
53
56
  end
54
57
 
55
58
  def timeout
@@ -70,12 +73,6 @@ module HTTPX
70
73
 
71
74
  private
72
75
 
73
- def connect
74
- return if @queries.empty?
75
-
76
- resolver_connection.__send__(__method__)
77
- end
78
-
79
76
  def pool
80
77
  Thread.current[:httpx_connection_pool] ||= Pool.new
81
78
  end
@@ -94,11 +91,18 @@ module HTTPX
94
91
  def resolve(connection = @connections.first, hostname = nil)
95
92
  return if @building_connection
96
93
 
97
- hostname = hostname || @queries.key(connection) || connection.origin.host
98
- type = @_record_types[hostname].first
94
+ hostname ||= @queries.key(connection)
95
+
96
+ if hostname.nil?
97
+ hostname = connection.origin.host
98
+ log { "resolver: resolve IDN #{connection.origin.non_ascii_hostname} as #{hostname}" } if connection.origin.non_ascii_hostname
99
+ end
100
+ type = @_record_types[hostname].first || "A"
99
101
  log { "resolver: query #{type} for #{hostname}" }
100
102
  begin
101
103
  request = build_request(hostname, type)
104
+ request.on(:response, &method(:on_response).curry(2)[request])
105
+ request.on(:promise, &method(:on_promise))
102
106
  @requests[request] = connection
103
107
  resolver_connection.send(request)
104
108
  @queries[hostname] = connection
@@ -113,9 +117,7 @@ module HTTPX
113
117
  rescue StandardError => e
114
118
  connection = @requests[request]
115
119
  hostname = @queries.key(connection)
116
- error = ResolveError.new("Can't resolve #{hostname}: #{e.message}")
117
- error.set_backtrace(e.backtrace)
118
- emit(:error, connection, error)
120
+ emit_resolve_error(connection, hostname, e)
119
121
  else
120
122
  parse(response)
121
123
  ensure
@@ -138,7 +140,7 @@ module HTTPX
138
140
  return
139
141
  end
140
142
  end
141
- if answers.empty?
143
+ if answers.nil? || answers.empty?
142
144
  host, connection = @queries.first
143
145
  @_record_types[host].shift
144
146
  if @_record_types[host].empty?
@@ -172,7 +174,7 @@ module HTTPX
172
174
  next unless connection # probably a retried query for which there's an answer
173
175
 
174
176
  @connections.delete(connection)
175
- Resolver.cached_lookup_set(hostname, addresses) if @resolver_options.cache
177
+ Resolver.cached_lookup_set(hostname, addresses) if @resolver_options[:cache]
176
178
  emit_addresses(connection, addresses.map { |addr| addr["data"] })
177
179
  end
178
180
  end
@@ -186,7 +188,7 @@ module HTTPX
186
188
  rklass = @options.request_class
187
189
  payload = Resolver.encode_dns_query(hostname, type: RECORD_TYPES[type])
188
190
 
189
- if @resolver_options.use_get
191
+ if @resolver_options[:use_get]
190
192
  params = URI.decode_www_form(uri.query.to_s)
191
193
  params << ["type", type]
192
194
  params << ["dns", Base64.urlsafe_encode64(payload, padding: false)]
@@ -197,8 +199,6 @@ module HTTPX
197
199
  request.headers["content-type"] = "application/dns-message"
198
200
  end
199
201
  request.headers["accept"] = "application/dns-message"
200
- request.on(:response, &method(:on_response).curry[request])
201
- request.on(:promise, &method(:on_promise))
202
202
  request
203
203
  end
204
204
 
@@ -206,7 +206,7 @@ module HTTPX
206
206
  case response.headers["content-type"]
207
207
  when "application/dns-json",
208
208
  "application/json",
209
- %r{^application\/x\-javascript} # because google...
209
+ %r{^application/x-javascript} # because google...
210
210
  payload = JSON.parse(response.to_s)
211
211
  payload["Answer"]
212
212
  when "application/dns-udpwireformat",
@@ -7,19 +7,18 @@ module HTTPX
7
7
  class Resolver::Native
8
8
  extend Forwardable
9
9
  include Resolver::ResolverMixin
10
+ using URIExtensions
10
11
 
11
- RESOLVE_TIMEOUT = 5
12
12
  RECORD_TYPES = {
13
13
  "A" => Resolv::DNS::Resource::IN::A,
14
14
  "AAAA" => Resolv::DNS::Resource::IN::AAAA,
15
15
  }.freeze
16
16
 
17
- # :nocov:
18
17
  DEFAULTS = if RUBY_VERSION < "2.2"
19
18
  {
20
19
  **Resolv::DNS::Config.default_config_hash,
21
20
  packet_size: 512,
22
- timeouts: RESOLVE_TIMEOUT,
21
+ timeouts: Resolver::RESOLVE_TIMEOUT,
23
22
  record_types: RECORD_TYPES.keys,
24
23
  }.freeze
25
24
  else
@@ -27,7 +26,7 @@ module HTTPX
27
26
  nameserver: nil,
28
27
  **Resolv::DNS::Config.default_config_hash,
29
28
  packet_size: 512,
30
- timeouts: RESOLVE_TIMEOUT,
29
+ timeouts: Resolver::RESOLVE_TIMEOUT,
31
30
  record_types: RECORD_TYPES.keys,
32
31
  }.freeze
33
32
  end
@@ -43,7 +42,6 @@ module HTTPX
43
42
  false
44
43
  end
45
44
  end if DEFAULTS[:nameserver]
46
- # :nocov:
47
45
 
48
46
  DNS_PORT = 53
49
47
 
@@ -52,15 +50,15 @@ module HTTPX
52
50
  def initialize(options)
53
51
  @options = Options.new(options)
54
52
  @ns_index = 0
55
- @resolver_options = Resolver::Options.new(DEFAULTS.merge(@options.resolver_options || {}))
56
- @nameserver = @resolver_options.nameserver
57
- @_timeouts = Array(@resolver_options.timeouts)
53
+ @resolver_options = DEFAULTS.merge(@options.resolver_options)
54
+ @nameserver = @resolver_options[:nameserver]
55
+ @_timeouts = Array(@resolver_options[:timeouts])
58
56
  @timeouts = Hash.new { |timeouts, host| timeouts[host] = @_timeouts.dup }
59
- @_record_types = Hash.new { |types, host| types[host] = @resolver_options.record_types.dup }
57
+ @_record_types = Hash.new { |types, host| types[host] = @resolver_options[:record_types].dup }
60
58
  @connections = []
61
59
  @queries = {}
62
60
  @read_buffer = "".b
63
- @write_buffer = Buffer.new(@resolver_options.packet_size)
61
+ @write_buffer = Buffer.new(@resolver_options[:packet_size])
64
62
  @state = :idle
65
63
  end
66
64
 
@@ -110,9 +108,9 @@ module HTTPX
110
108
  return if early_resolve(connection)
111
109
 
112
110
  if @nameserver.nil?
113
- ex = ResolveError.new("Can't resolve #{connection.origin.host}: no nameserver")
111
+ ex = ResolveError.new("No available nameserver")
114
112
  ex.set_backtrace(caller)
115
- emit(:error, connection, ex)
113
+ throw(:resolve_error, ex)
116
114
  else
117
115
  @connections << connection
118
116
  resolve
@@ -149,26 +147,30 @@ module HTTPX
149
147
  queries[h] = connection
150
148
  next
151
149
  end
150
+
152
151
  @timeouts[host].shift
153
152
  if @timeouts[host].empty?
154
153
  @timeouts.delete(host)
155
154
  @connections.delete(connection)
156
- raise NativeResolveError.new(connection, host)
155
+ # This loop_time passed to the exception is bogus. Ideally we would pass the total
156
+ # resolve timeout, including from the previous retries.
157
+ raise ResolveTimeoutError.new(loop_time, "Timed out")
158
+ # raise NativeResolveError.new(connection, host)
157
159
  else
160
+ log { "resolver: timeout after #{timeout}s, retry(#{@timeouts[host].first}) #{host}..." }
158
161
  connections << connection
159
- log { "resolver: timeout after #{prev_timeout}s, retry(#{timeouts.first}) #{host}..." }
162
+ queries[h] = connection
160
163
  end
161
164
  end
162
165
  @queries = queries
163
166
  connections.each { |ch| resolve(ch) }
164
167
  end
165
168
 
166
- def dread(wsize = @resolver_options.packet_size)
169
+ def dread(wsize = @resolver_options[:packet_size])
167
170
  loop do
168
171
  siz = @io.read(wsize, @read_buffer)
169
172
  return unless siz && siz.positive?
170
173
 
171
- log { "resolver: READ: #{siz} bytes..." }
172
174
  parse(@read_buffer)
173
175
  return if @state == :closed
174
176
  end
@@ -181,7 +183,6 @@ module HTTPX
181
183
  siz = @io.write(@write_buffer)
182
184
  return unless siz && siz.positive?
183
185
 
184
- log { "resolver: WRITE: #{siz} bytes..." }
185
186
  return if @state == :closed
186
187
  end
187
188
  end
@@ -200,13 +201,14 @@ module HTTPX
200
201
  end
201
202
  end
202
203
 
203
- if addresses.empty?
204
+ if addresses.nil? || addresses.empty?
204
205
  hostname, connection = @queries.first
205
206
  @_record_types[hostname].shift
206
207
  if @_record_types[hostname].empty?
207
208
  @queries.delete(hostname)
208
209
  @_record_types.delete(hostname)
209
210
  @connections.delete(connection)
211
+
210
212
  raise NativeResolveError.new(connection, hostname)
211
213
  end
212
214
  else
@@ -224,7 +226,7 @@ module HTTPX
224
226
  end
225
227
  else
226
228
  @connections.delete(connection)
227
- Resolver.cached_lookup_set(connection.origin.host, addresses) if @resolver_options.cache
229
+ Resolver.cached_lookup_set(connection.origin.host, addresses) if @resolver_options[:cache]
228
230
  emit_addresses(connection, addresses.map { |addr| addr["data"] })
229
231
  end
230
232
  end
@@ -237,9 +239,14 @@ module HTTPX
237
239
  raise Error, "no URI to resolve" unless connection
238
240
  return unless @write_buffer.empty?
239
241
 
240
- hostname = hostname || @queries.key(connection) || connection.origin.host
242
+ hostname ||= @queries.key(connection)
243
+
244
+ if hostname.nil?
245
+ hostname = connection.origin.host
246
+ log { "resolver: resolve IDN #{connection.origin.non_ascii_hostname} as #{hostname}" } if connection.origin.non_ascii_hostname
247
+ end
241
248
  @queries[hostname] = connection
242
- type = @_record_types[hostname].first
249
+ type = @_record_types[hostname].first || "A"
243
250
  log { "resolver: query #{type} for #{hostname}" }
244
251
  begin
245
252
  @write_buffer << Resolver.encode_dns_query(hostname, type: RECORD_TYPES[type])
@@ -276,7 +283,7 @@ module HTTPX
276
283
  @io.connect
277
284
  return unless @io.connected?
278
285
 
279
- resolve if @queries.empty?
286
+ resolve if @queries.empty? && !@connections.empty?
280
287
  when :closed
281
288
  return unless @state == :open
282
289