httpx 0.22.5 → 0.23.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 (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