excon 0.61.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 +30 -0
  7. data/data/cacert.pem +440 -994
  8. data/excon.gemspec +9 -0
  9. data/lib/excon/connection.rb +53 -36
  10. data/lib/excon/constants.rb +33 -13
  11. data/lib/excon/error.rb +3 -0
  12. data/lib/excon/instrumentors/logging_instrumentor.rb +3 -14
  13. data/lib/excon/instrumentors/standard_instrumentor.rb +1 -8
  14. data/lib/excon/middlewares/base.rb +6 -0
  15. data/lib/excon/middlewares/expects.rb +6 -0
  16. data/lib/excon/middlewares/idempotent.rb +20 -3
  17. data/lib/excon/middlewares/instrumentor.rb +8 -0
  18. data/lib/excon/middlewares/mock.rb +8 -0
  19. data/lib/excon/middlewares/response_parser.rb +3 -0
  20. data/lib/excon/pretty_printer.rb +1 -8
  21. data/lib/excon/socket.rb +36 -10
  22. data/lib/excon/ssl_socket.rb +7 -0
  23. data/lib/excon/utils.rb +23 -4
  24. data/lib/excon/version.rb +1 -1
  25. data/lib/excon.rb +9 -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 +13 -2
  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
data/excon.gemspec CHANGED
@@ -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
@@ -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
@@ -163,7 +172,7 @@ module Excon
163
172
  elsif body.nil?
164
173
  socket.write(request) # write out request + headers
165
174
  else # write out body
166
- if body.respond_to?(:binmode)
175
+ if body.respond_to?(:binmode) && !body.is_a?(StringIO)
167
176
  body.binmode
168
177
  end
169
178
  if body.respond_to?(:rewind)
@@ -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],
data/lib/excon/error.rb CHANGED
@@ -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)
@@ -2,6 +2,14 @@
2
2
  module Excon
3
3
  module Middleware
4
4
  class Instrumentor < Excon::Middleware::Base
5
+ def self.valid_parameter_keys
6
+ [
7
+ :logger,
8
+ :instrumentor,
9
+ :instrumentor_name
10
+ ]
11
+ end
12
+
5
13
  def error_call(datum)
6
14
  if datum.has_key?(:instrumentor)
7
15
  datum[:instrumentor].instrument("#{datum[:instrumentor_name]}.error", :error => datum[:error]) do
@@ -2,6 +2,14 @@
2
2
  module Excon
3
3
  module Middleware
4
4
  class Mock < Excon::Middleware::Base
5
+ def self.valid_parameter_keys
6
+ [
7
+ :allow_unstubbed_requests,
8
+ :captures,
9
+ :mock
10
+ ]
11
+ end
12
+
5
13
  def request_call(datum)
6
14
  if datum[:mock]
7
15
  # convert File/Tempfile body to string before matching:
@@ -6,6 +6,9 @@ module Excon
6
6
  unless datum.has_key?(:response)
7
7
  datum = Excon::Response.parse(datum[:connection].send(:socket), datum)
8
8
  end
9
+ if datum.has_key?(:logger)
10
+ datum[:response][:logger] = datum[:logger]
11
+ end
9
12
  @stack.response_call(datum)
10
13
  end
11
14
  end
@@ -9,14 +9,7 @@ module Excon
9
9
  datum.delete(:connection)
10
10
  datum.delete(:stack)
11
11
 
12
- if datum.has_key?(:headers) && datum[:headers].has_key?('Authorization')
13
- datum[:headers] = datum[:headers].dup
14
- datum[:headers]['Authorization'] = REDACTED
15
- end
16
-
17
- if datum.has_key?(:password)
18
- datum[:password] = REDACTED
19
- end
12
+ datum = Utils.redact(datum)
20
13
  end
21
14
 
22
15
  indent += 2
data/lib/excon/socket.rb CHANGED
@@ -7,6 +7,23 @@ module Excon
7
7
 
8
8
  attr_accessor :data
9
9
 
10
+ # read/write drawn from https://github.com/ruby-amqp/bunny/commit/75d9dd79551b31a5dd3d1254c537bad471f108cf
11
+ CONNECT_RETRY_EXCEPTION_CLASSES = if defined?(IO::EINPROGRESSWaitWritable) # Ruby >= 2.1
12
+ [Errno::EINPROGRESS, IO::EINPROGRESSWaitWritable]
13
+ else # Ruby <= 2.0
14
+ [Errno::EINPROGRESS]
15
+ end
16
+ READ_RETRY_EXCEPTION_CLASSES = if defined?(IO::EAGAINWaitReadable) # Ruby >= 2.1
17
+ [Errno::EAGAIN, Errno::EWOULDBLOCK, IO::WaitReadable, IO::EAGAINWaitReadable, IO::EWOULDBLOCKWaitReadable]
18
+ else # Ruby <= 2.0
19
+ [Errno::EAGAIN, Errno::EWOULDBLOCK, IO::WaitReadable]
20
+ end
21
+ WRITE_RETRY_EXCEPTION_CLASSES = if defined?(IO::EAGAINWaitWritable) # Ruby >= 2.1
22
+ [Errno::EAGAIN, Errno::EWOULDBLOCK, IO::WaitWritable, IO::EAGAINWaitWritable, IO::EWOULDBLOCKWaitWritable]
23
+ else # Ruby <= 2.0
24
+ [Errno::EAGAIN, Errno::EWOULDBLOCK, IO::WaitWritable]
25
+ end
26
+
10
27
  def params
11
28
  Excon.display_warning('Excon::Socket#params is deprecated use Excon::Socket#data instead.')
12
29
  @data
@@ -46,7 +63,7 @@ module Excon
46
63
  begin
47
64
  buffer << @socket.read_nonblock(1) while buffer[-1] != "\n"
48
65
  buffer
49
- rescue Errno::EAGAIN, Errno::EWOULDBLOCK, IO::WaitReadable
66
+ rescue *READ_RETRY_EXCEPTION_CLASSES
50
67
  select_with_timeout(@socket, :read) && retry
51
68
  rescue OpenSSL::SSL::SSLError => error
52
69
  if error.message == 'read would block'
@@ -106,7 +123,7 @@ module Excon
106
123
  if @socket
107
124
  break
108
125
  end
109
-
126
+
110
127
  @remote_ip = ip
111
128
 
112
129
  # nonblocking connect
@@ -128,7 +145,7 @@ module Excon
128
145
  socket.connect(sockaddr)
129
146
  end
130
147
  @socket = socket
131
- rescue Errno::EINPROGRESS
148
+ rescue *CONNECT_RETRY_EXCEPTION_CLASSES
132
149
  select_with_timeout(socket, :connect_write)
133
150
  begin
134
151
  socket.connect_nonblock(sockaddr)
@@ -151,6 +168,17 @@ module Excon
151
168
  ::Socket::TCP_NODELAY,
152
169
  true)
153
170
  end
171
+
172
+ if @data[:keepalive]
173
+ if [:SOL_SOCKET, :SO_KEEPALIVE, :SOL_TCP, :TCP_KEEPIDLE, :TCP_KEEPINTVL, :TCP_KEEPCNT].all?{|c| ::Socket.const_defined? c}
174
+ @socket.setsockopt(::Socket::SOL_SOCKET, ::Socket::SO_KEEPALIVE, true)
175
+ @socket.setsockopt(::Socket::SOL_TCP, ::Socket::TCP_KEEPIDLE, @data[:keepalive][:time])
176
+ @socket.setsockopt(::Socket::SOL_TCP, ::Socket::TCP_KEEPINTVL, @data[:keepalive][:intvl])
177
+ @socket.setsockopt(::Socket::SOL_TCP, ::Socket::TCP_KEEPCNT, @data[:keepalive][:probes])
178
+ else
179
+ Excon.display_warning('Excon::Socket keepalive was set, but is not supported by Ruby version.')
180
+ end
181
+ end
154
182
  end
155
183
 
156
184
  def read_nonblock(max_length)
@@ -170,7 +198,7 @@ module Excon
170
198
  else
171
199
  raise(error)
172
200
  end
173
- rescue Errno::EAGAIN, Errno::EWOULDBLOCK, IO::WaitReadable
201
+ rescue *READ_RETRY_EXCEPTION_CLASSES
174
202
  if @read_buffer.empty?
175
203
  # if we didn't read anything, try again...
176
204
  select_with_timeout(@socket, :read) && retry
@@ -199,7 +227,7 @@ module Excon
199
227
  else
200
228
  raise(error)
201
229
  end
202
- rescue Errno::EAGAIN, Errno::EWOULDBLOCK, IO::WaitReadable
230
+ rescue *READ_RETRY_EXCEPTION_CLASSES
203
231
  if @read_buffer.empty?
204
232
  select_with_timeout(@socket, :read) && retry
205
233
  end
@@ -208,9 +236,7 @@ module Excon
208
236
  end
209
237
 
210
238
  def write_nonblock(data)
211
- if FORCE_ENC
212
- data.force_encoding('BINARY')
213
- end
239
+ binary_encode(data)
214
240
  loop do
215
241
  written = nil
216
242
  begin
@@ -225,7 +251,7 @@ module Excon
225
251
  else
226
252
  raise error
227
253
  end
228
- rescue OpenSSL::SSL::SSLError, Errno::EAGAIN, Errno::EWOULDBLOCK, IO::WaitWritable => error
254
+ rescue OpenSSL::SSL::SSLError, *WRITE_RETRY_EXCEPTION_CLASSES => error
229
255
  if error.is_a?(OpenSSL::SSL::SSLError) && error.message != 'write would block'
230
256
  raise error
231
257
  else
@@ -246,7 +272,7 @@ module Excon
246
272
 
247
273
  def write_block(data)
248
274
  @socket.write(data)
249
- rescue OpenSSL::SSL::SSLError, Errno::EAGAIN, Errno::EWOULDBLOCK, IO::WaitWritable => error
275
+ rescue OpenSSL::SSL::SSLError, *WRITE_RETRY_EXCEPTION_CLASSES => error
250
276
  if error.is_a?(OpenSSL::SSL::SSLError) && error.message != 'write would block'
251
277
  raise error
252
278
  else
@@ -27,6 +27,13 @@ module Excon
27
27
  if @data[:ssl_version]
28
28
  ssl_context.ssl_version = @data[:ssl_version]
29
29
  end
30
+ if @data[:ssl_min_version]
31
+ ssl_context.min_version = @data[:ssl_min_version]
32
+ end
33
+ if @data[:ssl_max_version]
34
+ ssl_context.max_version = @data[:ssl_max_version]
35
+ end
36
+
30
37
 
31
38
  if @data[:ssl_verify_peer]
32
39
  # turn verification on