httpx 0.22.5 → 0.23.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (82) hide show
  1. checksums.yaml +4 -4
  2. data/doc/release_notes/0_23_0.md +42 -0
  3. data/lib/httpx/adapters/datadog.rb +1 -1
  4. data/lib/httpx/adapters/faraday.rb +1 -1
  5. data/lib/httpx/adapters/sentry.rb +4 -4
  6. data/lib/httpx/adapters/webmock.rb +2 -2
  7. data/lib/httpx/buffer.rb +4 -0
  8. data/lib/httpx/chainable.rb +4 -4
  9. data/lib/httpx/connection/http1.rb +2 -2
  10. data/lib/httpx/connection/http2.rb +2 -3
  11. data/lib/httpx/connection.rb +29 -10
  12. data/lib/httpx/io/udp.rb +2 -0
  13. data/lib/httpx/io/unix.rb +1 -5
  14. data/lib/httpx/io.rb +0 -10
  15. data/lib/httpx/options.rb +16 -2
  16. data/lib/httpx/plugins/authentication/digest.rb +1 -1
  17. data/lib/httpx/plugins/aws_sdk_authentication.rb +1 -3
  18. data/lib/httpx/plugins/aws_sigv4.rb +1 -1
  19. data/lib/httpx/plugins/compression/brotli.rb +4 -4
  20. data/lib/httpx/plugins/compression/deflate.rb +12 -7
  21. data/lib/httpx/plugins/compression/gzip.rb +7 -5
  22. data/lib/httpx/plugins/compression.rb +9 -8
  23. data/lib/httpx/plugins/digest_authentication.rb +1 -4
  24. data/lib/httpx/plugins/follow_redirects.rb +1 -1
  25. data/lib/httpx/plugins/grpc/message.rb +3 -1
  26. data/lib/httpx/plugins/grpc.rb +3 -3
  27. data/lib/httpx/plugins/h2c.rb +5 -9
  28. data/lib/httpx/plugins/internal_telemetry.rb +16 -0
  29. data/lib/httpx/plugins/multipart.rb +14 -2
  30. data/lib/httpx/plugins/proxy/http.rb +4 -4
  31. data/lib/httpx/plugins/proxy.rb +65 -31
  32. data/lib/httpx/plugins/response_cache.rb +2 -2
  33. data/lib/httpx/plugins/retries.rb +49 -2
  34. data/lib/httpx/plugins/upgrade/h2.rb +3 -3
  35. data/lib/httpx/plugins/upgrade.rb +4 -7
  36. data/lib/httpx/plugins/webdav.rb +7 -7
  37. data/lib/httpx/pool.rb +1 -1
  38. data/lib/httpx/request.rb +23 -13
  39. data/lib/httpx/resolver/https.rb +37 -20
  40. data/lib/httpx/resolver/native.rb +128 -36
  41. data/lib/httpx/resolver.rb +26 -14
  42. data/lib/httpx/response.rb +14 -16
  43. data/lib/httpx/session.rb +1 -0
  44. data/lib/httpx/transcoder/body.rb +0 -1
  45. data/lib/httpx/transcoder/chunker.rb +0 -1
  46. data/lib/httpx/transcoder/form.rb +0 -1
  47. data/lib/httpx/transcoder/json.rb +0 -1
  48. data/lib/httpx/transcoder/xml.rb +0 -1
  49. data/lib/httpx/transcoder.rb +0 -2
  50. data/lib/httpx/version.rb +1 -1
  51. data/lib/httpx.rb +0 -1
  52. data/sig/buffer.rbs +1 -0
  53. data/sig/chainable.rbs +3 -3
  54. data/sig/connection.rbs +17 -6
  55. data/sig/errors.rbs +9 -0
  56. data/sig/httpx.rbs +3 -3
  57. data/sig/io/ssl.rbs +17 -0
  58. data/sig/io/tcp.rbs +57 -0
  59. data/sig/io/udp.rbs +20 -0
  60. data/sig/io/unix.rbs +10 -0
  61. data/sig/options.rbs +5 -1
  62. data/sig/plugins/compression.rbs +6 -2
  63. data/sig/plugins/cookies/jar.rbs +2 -2
  64. data/sig/plugins/grpc.rbs +3 -3
  65. data/sig/plugins/h2c.rbs +1 -1
  66. data/sig/plugins/proxy.rbs +1 -5
  67. data/sig/plugins/response_cache.rbs +1 -1
  68. data/sig/plugins/retries.rbs +28 -8
  69. data/sig/plugins/upgrade.rbs +5 -3
  70. data/sig/request.rbs +6 -2
  71. data/sig/resolver/https.rbs +3 -1
  72. data/sig/resolver/native.rbs +7 -2
  73. data/sig/resolver/resolver.rbs +0 -2
  74. data/sig/resolver/system.rbs +2 -0
  75. data/sig/resolver.rbs +8 -4
  76. data/sig/response.rbs +6 -2
  77. data/sig/session.rbs +10 -10
  78. data/sig/transcoder/xml.rbs +1 -1
  79. data/sig/transcoder.rbs +4 -5
  80. metadata +9 -5
  81. data/lib/httpx/registry.rb +0 -85
  82. data/sig/registry.rbs +0 -13
@@ -32,6 +32,15 @@ module HTTPX
32
32
  end
33
33
  end
34
34
 
35
+ module NativeResolverMethods
36
+ def transition(nextstate)
37
+ state = @state
38
+ val = super
39
+ meter_elapsed_time("Resolver::Native: #{state} -> #{nextstate}")
40
+ val
41
+ end
42
+ end
43
+
35
44
  module InstanceMethods
36
45
  def self.included(klass)
37
46
  klass.prepend TrackTimeMethods
@@ -42,6 +51,13 @@ module HTTPX
42
51
  meter_elapsed_time("Session: initializing...")
43
52
  super
44
53
  meter_elapsed_time("Session: initialized!!!")
54
+ resolver_type = @options.resolver_class
55
+ resolver_type = Resolver.resolver_for(resolver_type)
56
+ return unless resolver_type <= Resolver::Native
57
+
58
+ resolver_type.prepend TrackTimeMethods
59
+ resolver_type.prepend NativeResolverMethods
60
+ @options = @options.merge(resolver_class: resolver_type)
45
61
  end
46
62
 
47
63
  def close(*)
@@ -40,9 +40,21 @@ module HTTPX
40
40
  require "httpx/plugins/multipart/part"
41
41
  require "httpx/plugins/multipart/mime_type_detector"
42
42
  end
43
+ end
44
+
45
+ module RequestBodyMethods
46
+ private
47
+
48
+ def initialize_body(options)
49
+ return FormTranscoder.encode(options.form) if options.form
50
+
51
+ super
52
+ end
53
+ end
43
54
 
44
- def configure(*)
45
- Transcoder.register("form", FormTranscoder)
55
+ module ResponseMethods
56
+ def form
57
+ decode(FormTranscoder)
46
58
  end
47
59
  end
48
60
 
@@ -61,7 +61,7 @@ module HTTPX
61
61
  return unless @io.connected?
62
62
 
63
63
  @parser || begin
64
- @parser = registry(@io.protocol).new(@write_buffer, @options.merge(max_concurrent_requests: 1))
64
+ @parser = self.class.parser_type(@io.protocol).new(@write_buffer, @options.merge(max_concurrent_requests: 1))
65
65
  parser = @parser
66
66
  parser.extend(ProxyParser)
67
67
  parser.on(:response, &method(:__http_on_connect))
@@ -141,9 +141,9 @@ module HTTPX
141
141
 
142
142
  module ProxyParser
143
143
  def join_headline(request)
144
- return super if request.verb == :connect
144
+ return super if request.verb == "CONNECT"
145
145
 
146
- "#{request.verb.to_s.upcase} #{request.uri} HTTP/#{@version.join(".")}"
146
+ "#{request.verb} #{request.uri} HTTP/#{@version.join(".")}"
147
147
  end
148
148
 
149
149
  def set_protocol_headers(request)
@@ -161,7 +161,7 @@ module HTTPX
161
161
 
162
162
  class ConnectRequest < Request
163
163
  def initialize(uri, _options)
164
- super(:connect, uri, {})
164
+ super("CONNECT", uri, {})
165
165
  @headers.delete("accept")
166
166
  end
167
167
 
@@ -18,6 +18,43 @@ module HTTPX
18
18
  Error = HTTPProxyError
19
19
  PROXY_ERRORS = [TimeoutError, IOError, SystemCallError, Error].freeze
20
20
 
21
+ class << self
22
+ def configure(klass)
23
+ klass.plugin(:"proxy/http")
24
+ klass.plugin(:"proxy/socks4")
25
+ klass.plugin(:"proxy/socks5")
26
+ end
27
+
28
+ if URI::Generic.methods.include?(:use_proxy?)
29
+ def use_proxy?(*args)
30
+ URI::Generic.use_proxy?(*args)
31
+ end
32
+ else
33
+ # https://github.com/ruby/uri/blob/ae07f956a4bea00b4f54a75bd40b8fa918103eed/lib/uri/generic.rb
34
+ def use_proxy?(hostname, addr, port, no_proxy)
35
+ hostname = hostname.downcase
36
+ dothostname = ".#{hostname}"
37
+ no_proxy.scan(/([^:,\s]+)(?::(\d+))?/) do |p_host, p_port|
38
+ if !p_port || port == p_port.to_i
39
+ if p_host.start_with?(".")
40
+ return false if hostname.end_with?(p_host.downcase)
41
+ else
42
+ return false if dothostname.end_with?(".#{p_host.downcase}")
43
+ end
44
+ if addr
45
+ begin
46
+ return false if IPAddr.new(p_host).include?(addr)
47
+ rescue IPAddr::InvalidAddressError
48
+ next
49
+ end
50
+ end
51
+ end
52
+ end
53
+ true
54
+ end
55
+ end
56
+ end
57
+
21
58
  class Parameters
22
59
  attr_reader :uri, :username, :password, :scheme
23
60
 
@@ -77,14 +114,6 @@ module HTTPX
77
114
  end
78
115
  end
79
116
 
80
- class << self
81
- def configure(klass)
82
- klass.plugin(:"proxy/http")
83
- klass.plugin(:"proxy/socks4")
84
- klass.plugin(:"proxy/socks5")
85
- end
86
- end
87
-
88
117
  module OptionsMethods
89
118
  def option_proxy(value)
90
119
  value.is_a?(Parameters) ? value : Hash[value]
@@ -94,34 +123,39 @@ module HTTPX
94
123
  module InstanceMethods
95
124
  private
96
125
 
97
- def proxy_uris(uri, options)
98
- @_proxy_uris ||= begin
99
- uris = options.proxy ? Array(options.proxy[:uri]) : []
100
- if uris.empty?
101
- uri = URI(uri).find_proxy
102
- uris << uri if uri
103
- end
104
- uris
105
- end
106
- return if @_proxy_uris.empty?
126
+ def find_connection(request, connections, options)
127
+ return super unless options.respond_to?(:proxy)
107
128
 
108
- proxy = options.proxy
129
+ uri = URI(request.uri)
109
130
 
110
- return { uri: uri.host } if proxy && proxy.key?(:no_proxy) && !Array(proxy[:no_proxy]).grep(uri.host).empty?
131
+ proxy_opts = if (next_proxy = uri.find_proxy)
132
+ { uri: next_proxy }
133
+ else
134
+ proxy = options.proxy
111
135
 
112
- proxy_opts = { uri: @_proxy_uris.first }
113
- proxy_opts = options.proxy.merge(proxy_opts) if options.proxy
114
- proxy_opts
115
- end
136
+ return super unless proxy
116
137
 
117
- def find_connection(request, connections, options)
118
- return super unless options.respond_to?(:proxy)
138
+ return super(request, connections, options.merge(proxy: nil)) unless proxy.key?(:uri)
119
139
 
120
- uri = URI(request.uri)
121
- next_proxy = proxy_uris(uri, options)
122
- raise Error, "Failed to connect to proxy" unless next_proxy
140
+ @_proxy_uris ||= Array(proxy[:uri])
141
+
142
+ next_proxy = @_proxy_uris.first
143
+ raise Error, "Failed to connect to proxy" unless next_proxy
144
+
145
+ if proxy.key?(:no_proxy)
146
+ next_proxy = URI(next_proxy)
147
+
148
+ no_proxy = proxy[:no_proxy]
149
+ no_proxy = no_proxy.join(",") if no_proxy.is_a?(Array)
150
+
151
+ return super(request, connections, options.merge(proxy: nil)) unless Proxy.use_proxy?(uri.host, next_proxy.host,
152
+ next_proxy.port, no_proxy)
153
+ end
154
+
155
+ proxy.merge(uri: next_proxy)
156
+ end
123
157
 
124
- proxy = Parameters.new(**next_proxy) unless next_proxy[:uri] == uri.host
158
+ proxy = Parameters.new(**proxy_opts)
125
159
 
126
160
  proxy_options = options.merge(proxy: proxy)
127
161
  connection = pool.find_connection(uri, proxy_options) || build_connection(uri, proxy_options)
@@ -284,7 +318,7 @@ module HTTPX
284
318
  register_plugin :proxy, Proxy
285
319
  end
286
320
 
287
- class ProxySSL < IO.registry["ssl"]
321
+ class ProxySSL < SSL
288
322
  def initialize(tcp, request_uri, options)
289
323
  @io = tcp.to_io
290
324
  super(request_uri, tcp.addresses, options)
@@ -8,7 +8,7 @@ module HTTPX
8
8
  # https://gitlab.com/os85/httpx/wikis/Response-Cache
9
9
  #
10
10
  module ResponseCache
11
- CACHEABLE_VERBS = %i[get head].freeze
11
+ CACHEABLE_VERBS = %w[GET HEAD].freeze
12
12
  CACHEABLE_STATUS_CODES = [200, 203, 206, 300, 301, 410].freeze
13
13
  private_constant :CACHEABLE_VERBS
14
14
  private_constant :CACHEABLE_STATUS_CODES
@@ -96,7 +96,7 @@ module HTTPX
96
96
 
97
97
  module RequestMethods
98
98
  def response_cache_key
99
- @response_cache_key ||= Digest::SHA1.hexdigest("httpx-response-cache-#{@verb}#{@uri}")
99
+ @response_cache_key ||= Digest::SHA1.hexdigest("httpx-response-cache-#{@verb}-#{@uri}")
100
100
  end
101
101
  end
102
102
 
@@ -11,7 +11,7 @@ module HTTPX
11
11
  MAX_RETRIES = 3
12
12
  # TODO: pass max_retries in a configure/load block
13
13
 
14
- IDEMPOTENT_METHODS = %i[get options head put delete].freeze
14
+ IDEMPOTENT_METHODS = %w[GET OPTIONS HEAD PUT DELETE].freeze
15
15
  RETRYABLE_ERRORS = [
16
16
  IOError,
17
17
  EOFError,
@@ -96,7 +96,7 @@ module HTTPX
96
96
  )
97
97
  # rubocop:enable Style/MultilineTernaryOperator
98
98
  )
99
- response.close if response.respond_to?(:close)
99
+ __try_partial_retry(request, response)
100
100
  log { "failed to get response, #{request.retries} tries to go..." }
101
101
  request.retries -= 1
102
102
  request.transition(:idle)
@@ -134,15 +134,62 @@ module HTTPX
134
134
  def __retryable_error?(ex)
135
135
  RETRYABLE_ERRORS.any? { |klass| ex.is_a?(klass) }
136
136
  end
137
+
138
+ #
139
+ # Atttempt to set the request to perform a partial range request.
140
+ # This happens if the peer server accepts byte-range requests, and
141
+ # the last response contains some body payload.
142
+ #
143
+ def __try_partial_retry(request, response)
144
+ response = response.response if response.is_a?(ErrorResponse)
145
+
146
+ return unless response
147
+
148
+ unless response.headers.key?("accept-ranges") &&
149
+ response.headers["accept-ranges"] == "bytes" && # there's nothing else supported though...
150
+ (original_body = response.body)
151
+ response.close if response.respond_to?(:close)
152
+ return
153
+ end
154
+
155
+ request.partial_response = response
156
+
157
+ size = original_body.bytesize
158
+
159
+ request.headers["range"] = "bytes=#{size}-"
160
+ end
137
161
  end
138
162
 
139
163
  module RequestMethods
140
164
  attr_accessor :retries
141
165
 
166
+ attr_writer :partial_response
167
+
142
168
  def initialize(*args)
143
169
  super
144
170
  @retries = @options.max_retries
145
171
  end
172
+
173
+ def response=(response)
174
+ if @partial_response
175
+ if response.is_a?(Response) && response.status == 206
176
+ response.from_partial_response(@partial_response)
177
+ else
178
+ @partial_response.close
179
+ end
180
+ @partial_response = nil
181
+ end
182
+
183
+ super
184
+ end
185
+ end
186
+
187
+ module ResponseMethods
188
+ def from_partial_response(response)
189
+ @status = response.status
190
+ @headers = response.headers
191
+ @body = response.body
192
+ end
146
193
  end
147
194
  end
148
195
  register_plugin :retries, Retries
@@ -10,8 +10,8 @@ module HTTPX
10
10
  #
11
11
  module H2
12
12
  class << self
13
- def configure(klass)
14
- klass.default_options.upgrade_handlers.register "h2", self
13
+ def extra_options(options)
14
+ options.merge(upgrade_handlers: options.upgrade_handlers.merge("h2" => self))
15
15
  end
16
16
 
17
17
  def call(connection, _request, _response)
@@ -32,7 +32,7 @@ module HTTPX
32
32
 
33
33
  @parser = Connection::HTTP2.new(@write_buffer, @options)
34
34
  set_parser_callbacks(@parser)
35
- @upgrade_protocol = :h2
35
+ @upgrade_protocol = "h2"
36
36
 
37
37
  # what's happening here:
38
38
  # a deviation from the state machine is done to perform the actions when a
@@ -15,16 +15,13 @@ module HTTPX
15
15
  end
16
16
 
17
17
  def extra_options(options)
18
- upgrade_handlers = Module.new do
19
- extend Registry
20
- end
21
- options.merge(upgrade_handlers: upgrade_handlers)
18
+ options.merge(upgrade_handlers: {})
22
19
  end
23
20
  end
24
21
 
25
22
  module OptionsMethods
26
23
  def option_upgrade_handlers(value)
27
- raise TypeError, ":upgrade_handlers must be a registry" unless value.respond_to?(:registry)
24
+ raise TypeError, ":upgrade_handlers must be a Hash" unless value.is_a?(Hash)
28
25
 
29
26
  value
30
27
  end
@@ -41,9 +38,9 @@ module HTTPX
41
38
 
42
39
  upgrade_protocol = response.headers["upgrade"].split(/ *, */).first
43
40
 
44
- return response unless upgrade_protocol && options.upgrade_handlers.registry.key?(upgrade_protocol)
41
+ return response unless upgrade_protocol && options.upgrade_handlers.key?(upgrade_protocol)
45
42
 
46
- protocol_handler = options.upgrade_handlers.registry(upgrade_protocol)
43
+ protocol_handler = options.upgrade_handlers[upgrade_protocol]
47
44
 
48
45
  return response unless protocol_handler
49
46
 
@@ -10,11 +10,11 @@ module HTTPX
10
10
  module WebDav
11
11
  module InstanceMethods
12
12
  def copy(src, dest)
13
- request(:copy, src, headers: { "destination" => @options.origin.merge(dest) })
13
+ request("COPY", src, headers: { "destination" => @options.origin.merge(dest) })
14
14
  end
15
15
 
16
16
  def move(src, dest)
17
- request(:move, src, headers: { "destination" => @options.origin.merge(dest) })
17
+ request("MOVE", src, headers: { "destination" => @options.origin.merge(dest) })
18
18
  end
19
19
 
20
20
  def lock(path, timeout: nil, &blk)
@@ -30,7 +30,7 @@ module HTTPX
30
30
  "<D:locktype><D:write/></D:locktype>" \
31
31
  "<D:owner>null</D:owner>" \
32
32
  "</D:lockinfo>"
33
- response = request(:lock, path, headers: headers, xml: xml)
33
+ response = request("LOCK", path, headers: headers, xml: xml)
34
34
 
35
35
  return response unless response.is_a?(Response)
36
36
 
@@ -46,11 +46,11 @@ module HTTPX
46
46
  end
47
47
 
48
48
  def unlock(path, lock_token)
49
- request(:unlock, path, headers: { "lock-token" => lock_token })
49
+ request("UNLOCK", path, headers: { "lock-token" => lock_token })
50
50
  end
51
51
 
52
52
  def mkcol(dir)
53
- request(:mkcol, dir)
53
+ request("MKCOL", dir)
54
54
  end
55
55
 
56
56
  def propfind(path, xml = nil)
@@ -64,13 +64,13 @@ module HTTPX
64
64
  xml
65
65
  end
66
66
 
67
- request(:propfind, path, headers: { "depth" => "1" }, xml: body)
67
+ request("PROPFIND", path, headers: { "depth" => "1" }, xml: body)
68
68
  end
69
69
 
70
70
  def proppatch(path, xml)
71
71
  body = "<?xml version=\"1.0\"?>" \
72
72
  "<D:propertyupdate xmlns:D=\"DAV:\" xmlns:Z=\"http://ns.example.com/standards/z39.50/\">#{xml}</D:propertyupdate>"
73
- request(:proppatch, path, xml: body)
73
+ request("PROPPATCH", path, xml: body)
74
74
  end
75
75
  # %i[ orderpatch acl report search]
76
76
  end
data/lib/httpx/pool.rb CHANGED
@@ -244,7 +244,7 @@ module HTTPX
244
244
  def find_resolver_for(connection)
245
245
  connection_options = connection.options
246
246
  resolver_type = connection_options.resolver_class
247
- resolver_type = Resolver.registry(resolver_type) if resolver_type.is_a?(Symbol)
247
+ resolver_type = Resolver.resolver_for(resolver_type)
248
248
 
249
249
  @resolvers[resolver_type] ||= begin
250
250
  resolver_manager = if resolver_type.multi?
data/lib/httpx/request.rb CHANGED
@@ -19,7 +19,11 @@ module HTTPX
19
19
  def_delegator :@body, :empty?
20
20
 
21
21
  def initialize(verb, uri, options = {})
22
- @verb = verb.to_s.downcase.to_sym
22
+ if verb.is_a?(Symbol)
23
+ warn "DEPRECATION WARNING: Using symbols for `verb` is deprecated, and will not be supported in httpx 1.0. " \
24
+ "Use \"#{verb.to_s.upcase}\" instead."
25
+ end
26
+ @verb = verb.to_s.upcase
23
27
  @options = Options.new(options)
24
28
  @uri = Utils.to_uri(uri)
25
29
  if @uri.relative?
@@ -116,7 +120,7 @@ module HTTPX
116
120
 
117
121
  query = []
118
122
  if (q = @options.params)
119
- query << Transcoder.registry("form").encode(q)
123
+ query << Transcoder::Form.encode(q)
120
124
  end
121
125
  query << @uri.query if @uri.query
122
126
  @query = query.join("&")
@@ -138,7 +142,7 @@ module HTTPX
138
142
  # :nocov:
139
143
  def inspect
140
144
  "#<HTTPX::Request:#{object_id} " \
141
- "#{@verb.to_s.upcase} " \
145
+ "#{@verb} " \
142
146
  "#{uri} " \
143
147
  "@headers=#{@headers} " \
144
148
  "@body=#{@body}>"
@@ -156,15 +160,7 @@ module HTTPX
156
160
 
157
161
  def initialize(headers, options)
158
162
  @headers = headers
159
- @body = if options.body
160
- Transcoder.registry("body").encode(options.body)
161
- elsif options.form
162
- Transcoder.registry("form").encode(options.form)
163
- elsif options.json
164
- Transcoder.registry("json").encode(options.json)
165
- elsif options.xml
166
- Transcoder.registry("xml").encode(options.xml)
167
- end
163
+ @body = initialize_body(options)
168
164
  return if @body.nil?
169
165
 
170
166
  @headers["content-type"] ||= @body.content_type
@@ -207,7 +203,7 @@ module HTTPX
207
203
 
208
204
  def stream(body)
209
205
  encoded = body
210
- encoded = Transcoder.registry("chunker").encode(body.enum_for(:each)) if chunked?
206
+ encoded = Transcoder::Chunker.encode(body.enum_for(:each)) if chunked?
211
207
  encoded
212
208
  end
213
209
 
@@ -231,6 +227,20 @@ module HTTPX
231
227
  "#{unbounded_body? ? "stream" : "@bytesize=#{bytesize}"}>"
232
228
  end
233
229
  # :nocov:
230
+
231
+ private
232
+
233
+ def initialize_body(options)
234
+ if options.body
235
+ Transcoder::Body.encode(options.body)
236
+ elsif options.form
237
+ Transcoder::Form.encode(options.form)
238
+ elsif options.json
239
+ Transcoder::JSON.encode(options.json)
240
+ elsif options.xml
241
+ Transcoder::Xml.encode(options.xml)
242
+ end
243
+ end
234
244
  end
235
245
 
236
246
  def transition(nextstate)
@@ -104,7 +104,7 @@ module HTTPX
104
104
  resolver_connection.send(request)
105
105
  @connections << connection
106
106
  rescue ResolveError, Resolv::DNS::EncodeError => e
107
- @queries.delete(hostname)
107
+ reset_hostname(hostname)
108
108
  emit_resolve_error(connection, connection.origin.host, e)
109
109
  end
110
110
  end
@@ -113,7 +113,7 @@ module HTTPX
113
113
  response.raise_for_status
114
114
  rescue StandardError => e
115
115
  hostname = @requests.delete(request)
116
- connection = @queries.delete(hostname)
116
+ connection = reset_hostname(hostname)
117
117
  emit_resolve_error(connection, connection.origin.host, e)
118
118
  else
119
119
  # @type var response: HTTPX::Response
@@ -128,30 +128,35 @@ module HTTPX
128
128
  end
129
129
 
130
130
  def parse(request, response)
131
- begin
132
- answers = decode_response_body(response)
133
- rescue Resolv::DNS::DecodeError => e
134
- host, connection = @queries.first
135
- @queries.delete(host)
136
- emit_resolve_error(connection, connection.origin.host, e)
137
- return
138
- end
131
+ code, result = decode_response_body(response)
139
132
 
140
- if answers.nil?
133
+ case code
134
+ when :ok
135
+ parse_addresses(result)
136
+ when :no_domain_found
141
137
  # Indicates no such domain was found.
142
138
 
143
139
  host = @requests.delete(request)
144
- connection = @queries.delete(host)
140
+ connection = reset_hostname(host)
145
141
 
146
- emit_resolve_error(connection) unless @queries.value?(connection)
147
- elsif answers.empty?
148
- # no address found, eliminate candidates
142
+ emit_resolve_error(connection)
143
+ when :dns_error
149
144
  host = @requests.delete(request)
150
- connection = @queries.delete(host)
145
+ connection = reset_hostname(host)
151
146
 
152
- # eliminate other candidates
153
- @queries.delete_if { |_, conn| connection == conn }
147
+ emit_resolve_error(connection)
148
+ when :decode_error
149
+ host, connection = @queries.first
150
+ reset_hostname(host)
151
+ emit_resolve_error(connection, connection.origin.host, result)
152
+ end
153
+ end
154
154
 
155
+ def parse_addresses(answers)
156
+ if answers.empty?
157
+ # no address found, eliminate candidates
158
+ host = @requests.delete(request)
159
+ connection = reset_hostname(host)
155
160
  emit_resolve_error(connection)
156
161
  return
157
162
 
@@ -162,7 +167,7 @@ module HTTPX
162
167
  if address.key?("alias")
163
168
  alias_address = answers[address["alias"]]
164
169
  if alias_address.nil?
165
- @queries.delete(address["name"])
170
+ reset_hostname(address["name"])
166
171
  if catch(:coalesced) { early_resolve(connection, hostname: address["alias"]) }
167
172
  @connections.delete(connection)
168
173
  else
@@ -179,7 +184,7 @@ module HTTPX
179
184
  next if addresses.empty?
180
185
 
181
186
  hostname.delete_suffix!(".") if hostname.end_with?(".")
182
- connection = @queries.delete(hostname)
187
+ connection = reset_hostname(hostname, reset_candidates: false)
183
188
  next unless connection # probably a retried query for which there's an answer
184
189
 
185
190
  @connections.delete(connection)
@@ -224,5 +229,17 @@ module HTTPX
224
229
  raise Error, "unsupported DNS mime-type (#{response.headers["content-type"]})"
225
230
  end
226
231
  end
232
+
233
+ def reset_hostname(hostname, reset_candidates: true)
234
+ connection = @queries.delete(hostname)
235
+
236
+ return connection unless connection && reset_candidates
237
+
238
+ # eliminate other candidates
239
+ candidates = @queries.select { |_, conn| connection == conn }.keys
240
+ @queries.delete_if { |h, _| candidates.include?(h) }
241
+
242
+ connection
243
+ end
227
244
  end
228
245
  end