excon 0.62.0 → 0.63.0

Sign up to get free protection for your applications and to get access to all the features.

Potentially problematic release.


This version of excon might be problematic. Click here for more details.

Files changed (54) hide show
  1. checksums.yaml +5 -5
  2. data/.github/stale.yml +17 -0
  3. data/.travis.yml +7 -19
  4. data/LICENSE.md +1 -1
  5. data/README.md +5 -4
  6. data/changelog.txt +25 -0
  7. data/data/cacert.pem +440 -994
  8. data/excon.gemspec +9 -0
  9. data/lib/excon.rb +9 -1
  10. data/lib/excon/connection.rb +52 -35
  11. data/lib/excon/constants.rb +33 -13
  12. data/lib/excon/error.rb +3 -0
  13. data/lib/excon/instrumentors/logging_instrumentor.rb +3 -14
  14. data/lib/excon/instrumentors/standard_instrumentor.rb +1 -8
  15. data/lib/excon/middlewares/base.rb +6 -0
  16. data/lib/excon/middlewares/expects.rb +6 -0
  17. data/lib/excon/middlewares/idempotent.rb +20 -3
  18. data/lib/excon/middlewares/instrumentor.rb +8 -0
  19. data/lib/excon/middlewares/mock.rb +8 -0
  20. data/lib/excon/middlewares/response_parser.rb +3 -0
  21. data/lib/excon/pretty_printer.rb +1 -8
  22. data/lib/excon/socket.rb +36 -10
  23. data/lib/excon/ssl_socket.rb +7 -0
  24. data/lib/excon/utils.rb +23 -4
  25. data/lib/excon/version.rb +1 -1
  26. data/spec/excon/test/server_spec.rb +2 -2
  27. data/spec/helpers/warning_helpers.rb +9 -0
  28. data/spec/requests/unix_socket_spec.rb +2 -10
  29. data/spec/requests/validation_spec.rb +80 -0
  30. data/spec/spec_helper.rb +2 -0
  31. data/spec/support/shared_contexts/test_stub_context.rb +11 -0
  32. data/spec/support/shared_examples/shared_example_for_clients.rb +6 -4
  33. data/tests/authorization_header_tests.rb +19 -21
  34. data/tests/bad_tests.rb +22 -0
  35. data/tests/batch_requests.rb +1 -1
  36. data/tests/complete_responses.rb +1 -1
  37. data/tests/data/127.0.0.1.cert.crt +15 -18
  38. data/tests/data/127.0.0.1.cert.key +28 -27
  39. data/tests/data/excon.cert.crt +15 -18
  40. data/tests/data/excon.cert.key +28 -27
  41. data/tests/error_tests.rb +1 -1
  42. data/tests/instrumentors/logging_instrumentor_tests.rb +28 -0
  43. data/tests/middlewares/decompress_tests.rb +1 -1
  44. data/tests/middlewares/idempotent_tests.rb +56 -17
  45. data/tests/middlewares/mock_tests.rb +2 -2
  46. data/tests/pipeline_tests.rb +1 -1
  47. data/tests/request_tests.rb +5 -6
  48. data/tests/response_tests.rb +1 -1
  49. data/tests/servers/bad.rb +5 -0
  50. data/tests/servers/good.rb +0 -8
  51. data/tests/servers/good_ipv4.rb +8 -0
  52. data/tests/servers/good_ipv6.rb +8 -0
  53. data/tests/test_helper.rb +27 -36
  54. metadata +17 -5
@@ -26,4 +26,13 @@ Gem::Specification.new do |s|
26
26
  s.add_development_dependency('sinatra-contrib')
27
27
  s.add_development_dependency('json', '>= 1.8.5')
28
28
  s.add_development_dependency('puma')
29
+
30
+ s.metadata = {
31
+ 'homepage_uri' => 'https://github.com/excon/excon',
32
+ 'bug_tracker_uri' => 'https://github.com/excon/excon/issues',
33
+ 'changelog_uri' => 'https://github.com/excon/excon/blob/master/changelog.txt',
34
+ 'documentation_uri' => 'https://github.com/excon/excon/blob/master/README.md',
35
+ 'source_code_uri' => 'https://github.com/excon/excon',
36
+ 'wiki_uri' => 'https://github.com/excon/excon/wiki'
37
+ }
29
38
  end
@@ -23,11 +23,11 @@ require 'excon/middlewares/instrumentor'
23
23
  require 'excon/middlewares/mock'
24
24
  require 'excon/middlewares/response_parser'
25
25
 
26
+ require 'excon/error'
26
27
  require 'excon/constants'
27
28
  require 'excon/utils'
28
29
 
29
30
  require 'excon/connection'
30
- require 'excon/error'
31
31
  require 'excon/headers'
32
32
  require 'excon/response'
33
33
  require 'excon/middlewares/decompress'
@@ -61,6 +61,14 @@ module Excon
61
61
  if $VERBOSE || ENV['EXCON_DEBUG']
62
62
  $stderr.puts "[excon][WARNING] #{warning}\n#{ caller.join("\n") }"
63
63
  end
64
+
65
+ if @raise_on_warnings
66
+ raise Error::Warning.new(warning)
67
+ end
68
+ end
69
+
70
+ def set_raise_on_warnings!(should_raise)
71
+ @raise_on_warnings = should_raise
64
72
  end
65
73
 
66
74
  # Status of mocking
@@ -38,8 +38,8 @@ module Excon
38
38
  end
39
39
  end
40
40
  def logger=(logger)
41
- Excon::LoggingInstrumentor.logger = logger
42
41
  @data[:instrumentor] = Excon::LoggingInstrumentor
42
+ @data[:logger] = logger
43
43
  end
44
44
 
45
45
  # Initializes a new Connection instance
@@ -67,8 +67,13 @@ module Excon
67
67
  # the same goes for :middlewares
68
68
  @data[:middlewares] = @data[:middlewares].dup
69
69
 
70
- params = validate_params(:connection, params)
71
70
  @data.merge!(params)
71
+ validate_params(:connection, @data, @data[:middlewares])
72
+
73
+ if @data.key?(:host) && !@data.key?(:hostname)
74
+ Excon.display_warning('hostname is missing! For IPv6 support, provide both host and hostname: Excon::Connection#new(:host => uri.host, :hostname => uri.hostname, ...).')
75
+ @data[:hostname] = @data[:host]
76
+ end
72
77
 
73
78
  setup_proxy
74
79
 
@@ -138,7 +143,13 @@ module Excon
138
143
 
139
144
  # add headers to request
140
145
  datum[:headers].each do |key, values|
146
+ if key.to_s.match(/[\r\n]/)
147
+ raise Excon::Errors::InvalidHeaderKey.new(key.to_s.inspect + ' contains forbidden "\r" or "\n"')
148
+ end
141
149
  [values].flatten.each do |value|
150
+ if value.to_s.match(/[\r\n]/)
151
+ raise Excon::Errors::InvalidHeaderValue.new(value.to_s.inspect + ' contains forbidden "\r" or "\n"')
152
+ end
142
153
  request << key.to_s << ': ' << value.to_s << CR_NL
143
154
  end
144
155
  end
@@ -150,9 +161,7 @@ module Excon
150
161
  socket.write(request) # write out request + headers
151
162
  while true # write out body with chunked encoding
152
163
  chunk = datum[:request_block].call
153
- if FORCE_ENC
154
- chunk.force_encoding('BINARY')
155
- end
164
+ binary_encode(chunk)
156
165
  if chunk.length > 0
157
166
  socket.write(chunk.length.to_s(16) << CR_NL << chunk << CR_NL)
158
167
  else
@@ -171,14 +180,10 @@ module Excon
171
180
  end
172
181
 
173
182
  # if request + headers is less than chunk size, fill with body
174
- if FORCE_ENC
175
- request.force_encoding('BINARY')
176
- end
183
+ binary_encode(request)
177
184
  chunk = body.read([datum[:chunk_size] - request.length, 0].max)
178
185
  if chunk
179
- if FORCE_ENC
180
- chunk.force_encoding('BINARY')
181
- end
186
+ binary_encode(chunk)
182
187
  socket.write(request << chunk)
183
188
  else
184
189
  socket.write(request) # write out request + headers
@@ -191,7 +196,7 @@ module Excon
191
196
  end
192
197
  rescue => error
193
198
  case error
194
- when Excon::Errors::StubNotFound, Excon::Errors::Timeout
199
+ when Excon::Errors::InvalidHeaderKey, Excon::Errors::InvalidHeaderValue, Excon::Errors::StubNotFound, Excon::Errors::Timeout
195
200
  raise(error)
196
201
  else
197
202
  raise_socket_error(error)
@@ -223,11 +228,17 @@ module Excon
223
228
  # @option params [String] :path appears after 'scheme://host:port/'
224
229
  # @option params [Hash] :query appended to the 'scheme://host:port/path/' in the form of '?key=value'
225
230
  def request(params={}, &block)
226
- params = validate_params(:request, params)
227
231
  # @data has defaults, merge in new params to override
228
232
  datum = @data.merge(params)
229
233
  datum[:headers] = @data[:headers].merge(datum[:headers] || {})
230
234
 
235
+ validate_params(:request, params, datum[:middlewares])
236
+ # If the user passed in new middleware, we want to validate that the original connection parameters
237
+ # are still valid with the provided middleware.
238
+ if params[:middlewares]
239
+ validate_params(:connection, @data, datum[:middlewares])
240
+ end
241
+
231
242
  if datum[:user] || datum[:password]
232
243
  user, pass = Utils.unescape_uri(datum[:user].to_s), Utils.unescape_uri(datum[:password].to_s)
233
244
  datum[:headers]['Authorization'] ||= 'Basic ' + ["#{user}:#{pass}"].pack('m').delete(Excon::CR_NL)
@@ -238,7 +249,6 @@ module Excon
238
249
  else
239
250
  datum[:headers]['Host'] ||= datum[:host] + port_string(datum)
240
251
  end
241
- datum[:retries_remaining] ||= datum[:retry_limit]
242
252
 
243
253
  # if path is empty or doesn't start with '/', insert one
244
254
  unless datum[:path][0, 1] == '/'
@@ -278,6 +288,10 @@ module Excon
278
288
  end
279
289
  rescue => error
280
290
  reset
291
+
292
+ # If we didn't get far enough to initialize datum and the middleware stack, just raise
293
+ raise error if !datum
294
+
281
295
  datum[:error] = error
282
296
  if datum[:stack]
283
297
  datum[:stack].error_call(datum)
@@ -355,15 +369,7 @@ module Excon
355
369
  vars = instance_variables.inject({}) do |accum, var|
356
370
  accum.merge!(var.to_sym => instance_variable_get(var))
357
371
  end
358
- if vars[:'@data'][:headers].has_key?('Authorization')
359
- vars[:'@data'] = vars[:'@data'].dup
360
- vars[:'@data'][:headers] = vars[:'@data'][:headers].dup
361
- vars[:'@data'][:headers]['Authorization'] = REDACTED
362
- end
363
- if vars[:'@data'][:password]
364
- vars[:'@data'] = vars[:'@data'].dup
365
- vars[:'@data'][:password] = REDACTED
366
- end
372
+ vars[:'@data'] = Utils.redact(vars[:'@data'])
367
373
  inspection = '#<Excon::Connection:'
368
374
  inspection += (object_id << 1).to_s(16)
369
375
  vars.each do |key, value|
@@ -373,6 +379,10 @@ module Excon
373
379
  inspection
374
380
  end
375
381
 
382
+ def valid_request_keys(middlewares)
383
+ valid_middleware_keys(middlewares) + Excon::VALID_REQUEST_KEYS
384
+ end
385
+
376
386
  private
377
387
 
378
388
  def detect_content_length(body)
@@ -387,27 +397,34 @@ module Excon
387
397
  end
388
398
  end
389
399
 
390
- def validate_params(validation, params)
400
+ def valid_middleware_keys(middlewares)
401
+ middlewares.flat_map(&:valid_parameter_keys)
402
+ end
403
+
404
+ def validate_params(validation, params, middlewares)
391
405
  valid_keys = case validation
392
406
  when :connection
393
- Excon::VALID_CONNECTION_KEYS
407
+ valid_middleware_keys(middlewares) + Excon::VALID_CONNECTION_KEYS
394
408
  when :request
395
- Excon::VALID_REQUEST_KEYS
409
+ valid_request_keys(middlewares)
410
+ else
411
+ raise ArgumentError.new("Invalid validation type '#{validation}'")
396
412
  end
413
+
397
414
  invalid_keys = params.keys - valid_keys
398
415
  unless invalid_keys.empty?
399
416
  Excon.display_warning("Invalid Excon #{validation} keys: #{invalid_keys.map(&:inspect).join(', ')}")
400
- # FIXME: for now, just warn, don't mutate, give things (ie fog) a chance to catch up
401
- #params = params.dup
402
- #invalid_keys.each {|key| params.delete(key) }
403
- end
404
417
 
405
- if validation == :connection && params.key?(:host) && !params.key?(:hostname)
406
- Excon.display_warning('hostname is missing! For IPv6 support, provide both host and hostname: Excon::Connection#new(:host => uri.host, :hostname => uri.hostname, ...).')
407
- params[:hostname] = params[:host]
418
+ if validation == :request
419
+ deprecated_keys = invalid_keys & Excon::DEPRECATED_VALID_REQUEST_KEYS.keys
420
+ mw_msg = deprecated_keys.map do |k|
421
+ "#{k}: #{Excon::DEPRECATED_VALID_REQUEST_KEYS[k]}"
422
+ end.join(', ')
423
+ Excon.display_warning(
424
+ "The following request keys are only valid with the associated middleware: #{mw_msg}"
425
+ )
426
+ end
408
427
  end
409
-
410
- params
411
428
  end
412
429
 
413
430
  def response(datum={})
@@ -14,6 +14,12 @@ module Excon
14
14
 
15
15
  DEFAULT_RETRY_LIMIT = 4
16
16
 
17
+ DEFAULT_RETRY_ERRORS = [
18
+ Excon::Error::Timeout,
19
+ Excon::Error::Socket,
20
+ Excon::Error::HTTPStatus
21
+ ]
22
+
17
23
  FORCE_ENC = CR_NL.respond_to?(:force_encoding)
18
24
 
19
25
  HTTP_1_1 = " HTTP/1.1\r\n"
@@ -33,19 +39,17 @@ module Excon
33
39
  VERSIONS = "#{USER_AGENT} (#{RUBY_PLATFORM}) ruby/#{RUBY_VERSION}"
34
40
 
35
41
  VALID_REQUEST_KEYS = [
42
+ :allow_unstubbed_requests,
36
43
  :body,
37
- :captures,
38
44
  :chunk_size,
39
45
  :debug_request,
40
46
  :debug_response,
41
- :expects,
42
47
  :headers,
43
- :idempotent,
44
- :instrumentor,
45
- :instrumentor_name,
48
+ :instrumentor, # Used for setting logging within Connection
49
+ :logger,
46
50
  :method,
47
51
  :middlewares,
48
- :mock,
52
+ :password,
49
53
  :path,
50
54
  :persistent,
51
55
  :pipeline,
@@ -53,9 +57,8 @@ module Excon
53
57
  :read_timeout,
54
58
  :request_block,
55
59
  :response_block,
56
- :retries_remaining, # used internally
57
- :retry_limit,
58
- :retry_interval,
60
+ :stubs,
61
+ :user,
59
62
  :versions,
60
63
  :write_timeout
61
64
  ]
@@ -74,12 +77,12 @@ module Excon
74
77
  :private_key_path,
75
78
  :connect_timeout,
76
79
  :family,
80
+ :keepalive,
77
81
  :host,
78
82
  :hostname,
79
83
  :omit_default_port,
80
84
  :nonblock,
81
85
  :reuseaddr,
82
- :password,
83
86
  :port,
84
87
  :proxy,
85
88
  :scheme,
@@ -91,12 +94,26 @@ module Excon
91
94
  :ssl_verify_peer,
92
95
  :ssl_verify_peer_host,
93
96
  :ssl_version,
97
+ :ssl_min_version,
98
+ :ssl_max_version,
99
+ :ssl_uri_schemes,
94
100
  :tcp_nodelay,
95
101
  :thread_safe_sockets,
96
102
  :uri_parser,
97
- :user
98
103
  ]
99
104
 
105
+ DEPRECATED_VALID_REQUEST_KEYS = {
106
+ :captures => 'Mock',
107
+ :expects => 'Expects',
108
+ :idempotent => 'Idempotent',
109
+ :instrumentor_name => 'Instrumentor',
110
+ :mock => 'Mock',
111
+ :retries_remaining => 'Idempotent', # referenced in Instrumentor, but only relevant with Idempotent
112
+ :retry_errors => 'Idempotent',
113
+ :retry_interval => 'Idempotent',
114
+ :retry_limit => 'Idempotent' # referenced in Instrumentor, but only relevant with Idempotent
115
+ }
116
+
100
117
  unless ::IO.const_defined?(:WaitReadable)
101
118
  class ::IO
102
119
  module WaitReadable; end
@@ -112,12 +129,14 @@ module Excon
112
129
  DEFAULTS = {
113
130
  :chunk_size => CHUNK_SIZE || DEFAULT_CHUNK_SIZE,
114
131
  # see https://wiki.mozilla.org/Security/Server_Side_TLS#Intermediate_compatibility_.28default.29
115
- :ciphers => 'ECDHE-ECDSA-CHACHA20-POLY1305:ECDHE-RSA-CHACHA20-POLY1305:ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384:DHE-RSA-AES128-GCM-SHA256:DHE-RSA-AES256-GCM-SHA384:ECDHE-ECDSA-AES128-SHA256:ECDHE-RSA-AES128-SHA256:ECDHE-ECDSA-AES128-SHA:ECDHE-RSA-AES256-SHA384:ECDHE-RSA-AES128-SHA:ECDHE-ECDSA-AES256-SHA384:ECDHE-ECDSA-AES256-SHA:ECDHE-RSA-AES256-SHA:DHE-RSA-AES128-SHA256:DHE-RSA-AES128-SHA:DHE-RSA-AES256-SHA256:DHE-RSA-AES256-SHA:ECDHE-ECDSA-DES-CBC3-SHA:ECDHE-RSA-DES-CBC3-SHA:EDH-RSA-DES-CBC3-SHA:AES128-GCM-SHA256:AES256-GCM-SHA384:AES128-SHA256:AES256-SHA256:AES128-SHA:AES256-SHA:DES-CBC3-SHA:!DSS',
132
+ # list provided then had DES related things sorted to the end
133
+ :ciphers => 'ECDHE-ECDSA-CHACHA20-POLY1305:ECDHE-RSA-CHACHA20-POLY1305:ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384:DHE-RSA-AES128-GCM-SHA256:DHE-RSA-AES256-GCM-SHA384:ECDHE-ECDSA-AES128-SHA256:ECDHE-RSA-AES128-SHA256:ECDHE-ECDSA-AES128-SHA:ECDHE-RSA-AES256-SHA384:ECDHE-RSA-AES128-SHA:ECDHE-ECDSA-AES256-SHA384:ECDHE-ECDSA-AES256-SHA:ECDHE-RSA-AES256-SHA:DHE-RSA-AES128-SHA256:DHE-RSA-AES128-SHA:DHE-RSA-AES256-SHA256:DHE-RSA-AES256-SHA:AES128-GCM-SHA256:AES256-GCM-SHA384:AES128-SHA256:AES256-SHA256:AES128-SHA:AES256-SHA:ECDHE-ECDSA-DES-CBC3-SHA:ECDHE-RSA-DES-CBC3-SHA:EDH-RSA-DES-CBC3-SHA:DES-CBC3-SHA:!DSS',
116
134
  :connect_timeout => 60,
117
135
  :debug_request => false,
118
136
  :debug_response => false,
119
137
  :headers => {
120
- 'User-Agent' => USER_AGENT
138
+ 'User-Agent' => USER_AGENT,
139
+ 'Accept' => '*/*'
121
140
  },
122
141
  :idempotent => false,
123
142
  :instrumentor_name => 'excon',
@@ -133,6 +152,7 @@ module Excon
133
152
  :omit_default_port => false,
134
153
  :persistent => false,
135
154
  :read_timeout => 60,
155
+ :retry_errors => DEFAULT_RETRY_ERRORS,
136
156
  :retry_limit => DEFAULT_RETRY_LIMIT,
137
157
  :ssl_verify_peer => true,
138
158
  :ssl_uri_schemes => [HTTPS],
@@ -6,6 +6,7 @@ module Excon
6
6
 
7
7
  class StubNotFound < Error; end
8
8
  class InvalidStub < Error; end
9
+ class Warning < Error; end
9
10
 
10
11
  # Socket related errors
11
12
  class Socket < Error
@@ -45,6 +46,8 @@ or:
45
46
  end
46
47
  end
47
48
 
49
+ class InvalidHeaderKey < Error; end
50
+ class InvalidHeaderValue < Error; end
48
51
  class Timeout < Error; end
49
52
  class ResponseParse < Error; end
50
53
  class ProxyParse < Error; end
@@ -2,23 +2,12 @@ require 'logger'
2
2
 
3
3
  module Excon
4
4
  class LoggingInstrumentor
5
- # Returns the Logger object for the LoggingInstrumentor. If one doesn't
6
- # already exist, then one will be created using $stderr as the output
7
- # stream.
8
- #
9
- def self.logger
10
- @logger ||= Logger.new($stderr)
11
- end
12
-
13
- # Sets the logger object for the LoggingInstrumentor.
14
- #
15
- def self.logger=(logger)
16
- @logger = logger
17
- end
18
5
 
19
6
  def self.instrument(name, params = {}, &block)
20
7
  params = params.dup
21
8
 
9
+ logger = params[:logger] || Logger.new($stderr)
10
+
22
11
  # reduce duplication/noise of output
23
12
  params.delete(:connection)
24
13
  params.delete(:stack)
@@ -51,7 +40,7 @@ module Excon
51
40
  end
52
41
  end
53
42
 
54
- self.logger.log(logger.level, info) if info
43
+ logger.log(Logger::INFO, info) if info
55
44
 
56
45
  yield if block_given?
57
46
  end
@@ -8,14 +8,7 @@ module Excon
8
8
  params.delete(:connection)
9
9
  params.delete(:stack)
10
10
 
11
- if params.has_key?(:headers) && params[:headers].has_key?('Authorization')
12
- params[:headers] = params[:headers].dup
13
- params[:headers]['Authorization'] = REDACTED
14
- end
15
-
16
- if params.has_key?(:password)
17
- params[:password] = REDACTED
18
- end
11
+ params = Utils.redact(params)
19
12
 
20
13
  $stderr.puts(name)
21
14
  Excon::PrettyPrinter.pp($stderr, params)
@@ -2,6 +2,12 @@
2
2
  module Excon
3
3
  module Middleware
4
4
  class Base
5
+ # Returns the list of parameters that this middleware uses that are valid
6
+ # as arguments to `Connection#request` or `Connection#new`.
7
+ def self.valid_parameter_keys
8
+ []
9
+ end
10
+
5
11
  def initialize(stack)
6
12
  @stack = stack
7
13
  end
@@ -2,6 +2,12 @@
2
2
  module Excon
3
3
  module Middleware
4
4
  class Expects < Excon::Middleware::Base
5
+ def self.valid_parameter_keys
6
+ [
7
+ :expects
8
+ ]
9
+ end
10
+
5
11
  def response_call(datum)
6
12
  if datum.has_key?(:expects) && ![*datum[:expects]].include?(datum[:response][:status])
7
13
  raise(
@@ -1,7 +1,24 @@
1
1
  # frozen_string_literal: true
2
+ require 'set'
3
+
2
4
  module Excon
3
5
  module Middleware
4
6
  class Idempotent < Excon::Middleware::Base
7
+ def self.valid_parameter_keys
8
+ [
9
+ :idempotent,
10
+ :retries_remaining,
11
+ :retry_errors,
12
+ :retry_interval,
13
+ :retry_limit
14
+ ]
15
+ end
16
+
17
+ def request_call(datum)
18
+ datum[:retries_remaining] ||= datum[:retry_limit]
19
+ @stack.request_call(datum)
20
+ end
21
+
5
22
  def error_call(datum)
6
23
  if datum[:idempotent]
7
24
  if datum.has_key?(:request_block)
@@ -21,15 +38,15 @@ module Excon
21
38
  end
22
39
  end
23
40
 
24
- if datum[:idempotent] && [Excon::Errors::Timeout, Excon::Errors::SocketError,
25
- Excon::Errors::HTTPStatusError].any? {|ex| datum[:error].kind_of?(ex) } && datum[:retries_remaining] > 1
41
+ if datum[:idempotent] && datum[:retry_errors].any? {|ex| datum[:error].kind_of?(ex) } && datum[:retries_remaining] > 1
26
42
 
27
43
  sleep(datum[:retry_interval]) if datum[:retry_interval]
28
44
 
29
45
  # reduces remaining retries, reset connection, and restart request_call
30
46
  datum[:retries_remaining] -= 1
31
47
  connection = datum.delete(:connection)
32
- datum.reject! {|key, _| !Excon::VALID_REQUEST_KEYS.include?(key) }
48
+ valid_keys = Set.new(connection.valid_request_keys(datum[:middlewares]))
49
+ datum.select! {|key, _| valid_keys.include?(key) }
33
50
  connection.request(datum)
34
51
  else
35
52
  @stack.error_call(datum)