excon 0.62.0 → 0.92.3

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 (125) hide show
  1. checksums.yaml +5 -5
  2. data/CONTRIBUTING.md +0 -1
  3. data/LICENSE.md +1 -1
  4. data/README.md +7 -6
  5. data/data/cacert.pem +1220 -1828
  6. data/excon.gemspec +19 -3
  7. data/lib/excon/connection.rb +216 -139
  8. data/lib/excon/constants.rb +38 -13
  9. data/lib/excon/error.rb +15 -0
  10. data/lib/excon/headers.rb +4 -3
  11. data/lib/excon/instrumentors/logging_instrumentor.rb +5 -16
  12. data/lib/excon/instrumentors/standard_instrumentor.rb +2 -9
  13. data/lib/excon/middlewares/base.rb +6 -0
  14. data/lib/excon/middlewares/capture_cookies.rb +1 -1
  15. data/lib/excon/middlewares/decompress.rb +11 -4
  16. data/lib/excon/middlewares/expects.rb +7 -1
  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 +12 -3
  20. data/lib/excon/middlewares/redirect_follower.rb +25 -3
  21. data/lib/excon/middlewares/response_parser.rb +3 -0
  22. data/lib/excon/pretty_printer.rb +1 -8
  23. data/lib/excon/response.rb +12 -9
  24. data/lib/excon/socket.rb +59 -42
  25. data/lib/excon/ssl_socket.rb +37 -15
  26. data/lib/excon/test/plugin/server/exec.rb +5 -2
  27. data/lib/excon/test/plugin/server/puma.rb +4 -1
  28. data/lib/excon/test/plugin/server/unicorn.rb +5 -0
  29. data/lib/excon/test/plugin/server/webrick.rb +4 -1
  30. data/lib/excon/test/server.rb +1 -1
  31. data/lib/excon/unix_socket.rb +1 -0
  32. data/lib/excon/utils.rb +59 -5
  33. data/lib/excon/version.rb +1 -1
  34. data/lib/excon.rb +25 -17
  35. metadata +27 -98
  36. data/.document +0 -5
  37. data/.gitignore +0 -13
  38. data/.rspec +0 -3
  39. data/.travis.yml +0 -29
  40. data/Gemfile +0 -19
  41. data/Rakefile +0 -41
  42. data/benchmarks/class_vs_lambda.rb +0 -50
  43. data/benchmarks/concat_vs_insert.rb +0 -21
  44. data/benchmarks/concat_vs_interpolate.rb +0 -22
  45. data/benchmarks/cr_lf.rb +0 -21
  46. data/benchmarks/downcase-eq-eq_vs_casecmp.rb +0 -169
  47. data/benchmarks/excon.rb +0 -69
  48. data/benchmarks/excon_vs.rb +0 -165
  49. data/benchmarks/for_vs_array_each.rb +0 -27
  50. data/benchmarks/for_vs_hash_each.rb +0 -27
  51. data/benchmarks/has_key-vs-lookup.rb +0 -177
  52. data/benchmarks/headers_case_sensitivity.rb +0 -83
  53. data/benchmarks/headers_split_vs_match.rb +0 -34
  54. data/benchmarks/implicit_block-vs-explicit_block.rb +0 -98
  55. data/benchmarks/merging.rb +0 -21
  56. data/benchmarks/single_vs_double_quotes.rb +0 -21
  57. data/benchmarks/string_ranged_index.rb +0 -87
  58. data/benchmarks/strip_newline.rb +0 -115
  59. data/benchmarks/vs_stdlib.rb +0 -82
  60. data/changelog.txt +0 -1083
  61. data/spec/excon/error_spec.rb +0 -139
  62. data/spec/excon/test/server_spec.rb +0 -28
  63. data/spec/excon_spec.rb +0 -7
  64. data/spec/helpers/file_path_helpers.rb +0 -22
  65. data/spec/requests/basic_spec.rb +0 -40
  66. data/spec/requests/eof_requests_spec.rb +0 -36
  67. data/spec/requests/unix_socket_spec.rb +0 -46
  68. data/spec/spec_helper.rb +0 -24
  69. data/spec/support/shared_contexts/test_server_context.rb +0 -83
  70. data/spec/support/shared_examples/shared_example_for_clients.rb +0 -218
  71. data/spec/support/shared_examples/shared_example_for_streaming_clients.rb +0 -20
  72. data/spec/support/shared_examples/shared_example_for_test_servers.rb +0 -16
  73. data/tests/authorization_header_tests.rb +0 -29
  74. data/tests/bad_tests.rb +0 -47
  75. data/tests/basic_tests.rb +0 -351
  76. data/tests/batch_requests.rb +0 -133
  77. data/tests/complete_responses.rb +0 -31
  78. data/tests/data/127.0.0.1.cert.crt +0 -20
  79. data/tests/data/127.0.0.1.cert.key +0 -27
  80. data/tests/data/excon.cert.crt +0 -20
  81. data/tests/data/excon.cert.key +0 -27
  82. data/tests/data/xs +0 -1
  83. data/tests/error_tests.rb +0 -145
  84. data/tests/header_tests.rb +0 -119
  85. data/tests/middlewares/canned_response_tests.rb +0 -34
  86. data/tests/middlewares/capture_cookies_tests.rb +0 -34
  87. data/tests/middlewares/decompress_tests.rb +0 -157
  88. data/tests/middlewares/escape_path_tests.rb +0 -36
  89. data/tests/middlewares/idempotent_tests.rb +0 -206
  90. data/tests/middlewares/instrumentation_tests.rb +0 -315
  91. data/tests/middlewares/mock_tests.rb +0 -304
  92. data/tests/middlewares/redirect_follower_tests.rb +0 -112
  93. data/tests/pipeline_tests.rb +0 -40
  94. data/tests/proxy_tests.rb +0 -306
  95. data/tests/query_string_tests.rb +0 -87
  96. data/tests/rackups/basic.rb +0 -41
  97. data/tests/rackups/basic.ru +0 -3
  98. data/tests/rackups/basic_auth.ru +0 -14
  99. data/tests/rackups/deflater.ru +0 -4
  100. data/tests/rackups/proxy.ru +0 -18
  101. data/tests/rackups/query_string.ru +0 -13
  102. data/tests/rackups/redirecting.ru +0 -23
  103. data/tests/rackups/redirecting_with_cookie.ru +0 -40
  104. data/tests/rackups/request_headers.ru +0 -15
  105. data/tests/rackups/request_methods.ru +0 -21
  106. data/tests/rackups/response_header.ru +0 -18
  107. data/tests/rackups/ssl.ru +0 -16
  108. data/tests/rackups/ssl_mismatched_cn.ru +0 -15
  109. data/tests/rackups/ssl_verify_peer.ru +0 -16
  110. data/tests/rackups/streaming.ru +0 -30
  111. data/tests/rackups/thread_safety.ru +0 -17
  112. data/tests/rackups/timeout.ru +0 -14
  113. data/tests/rackups/webrick_patch.rb +0 -34
  114. data/tests/request_headers_tests.rb +0 -21
  115. data/tests/request_method_tests.rb +0 -47
  116. data/tests/request_tests.rb +0 -59
  117. data/tests/response_tests.rb +0 -197
  118. data/tests/servers/bad.rb +0 -20
  119. data/tests/servers/eof.rb +0 -17
  120. data/tests/servers/error.rb +0 -20
  121. data/tests/servers/good.rb +0 -350
  122. data/tests/test_helper.rb +0 -306
  123. data/tests/thread_safety_tests.rb +0 -39
  124. data/tests/timeout_tests.rb +0 -12
  125. data/tests/utils_tests.rb +0 -81
@@ -12,8 +12,16 @@ module Excon
12
12
  CHUNK_SIZE = DEFAULT_CHUNK_SIZE
13
13
  end
14
14
 
15
+ DEFAULT_REDIRECT_LIMIT = 10
16
+
15
17
  DEFAULT_RETRY_LIMIT = 4
16
18
 
19
+ DEFAULT_RETRY_ERRORS = [
20
+ Excon::Error::Timeout,
21
+ Excon::Error::Socket,
22
+ Excon::Error::HTTPStatus
23
+ ]
24
+
17
25
  FORCE_ENC = CR_NL.respond_to?(:force_encoding)
18
26
 
19
27
  HTTP_1_1 = " HTTP/1.1\r\n"
@@ -33,19 +41,17 @@ module Excon
33
41
  VERSIONS = "#{USER_AGENT} (#{RUBY_PLATFORM}) ruby/#{RUBY_VERSION}"
34
42
 
35
43
  VALID_REQUEST_KEYS = [
44
+ :allow_unstubbed_requests,
36
45
  :body,
37
- :captures,
38
46
  :chunk_size,
39
47
  :debug_request,
40
48
  :debug_response,
41
- :expects,
42
49
  :headers,
43
- :idempotent,
44
- :instrumentor,
45
- :instrumentor_name,
50
+ :instrumentor, # Used for setting logging within Connection
51
+ :logger,
46
52
  :method,
47
53
  :middlewares,
48
- :mock,
54
+ :password,
49
55
  :path,
50
56
  :persistent,
51
57
  :pipeline,
@@ -53,9 +59,8 @@ module Excon
53
59
  :read_timeout,
54
60
  :request_block,
55
61
  :response_block,
56
- :retries_remaining, # used internally
57
- :retry_limit,
58
- :retry_interval,
62
+ :stubs,
63
+ :user,
59
64
  :versions,
60
65
  :write_timeout
61
66
  ]
@@ -74,12 +79,12 @@ module Excon
74
79
  :private_key_path,
75
80
  :connect_timeout,
76
81
  :family,
82
+ :keepalive,
77
83
  :host,
78
84
  :hostname,
79
85
  :omit_default_port,
80
86
  :nonblock,
81
87
  :reuseaddr,
82
- :password,
83
88
  :port,
84
89
  :proxy,
85
90
  :scheme,
@@ -90,13 +95,30 @@ module Excon
90
95
  :ssl_verify_callback,
91
96
  :ssl_verify_peer,
92
97
  :ssl_verify_peer_host,
98
+ :ssl_verify_hostname,
93
99
  :ssl_version,
100
+ :ssl_min_version,
101
+ :ssl_max_version,
102
+ :ssl_security_level,
103
+ :ssl_proxy_headers,
104
+ :ssl_uri_schemes,
94
105
  :tcp_nodelay,
95
106
  :thread_safe_sockets,
96
107
  :uri_parser,
97
- :user
98
108
  ]
99
109
 
110
+ DEPRECATED_VALID_REQUEST_KEYS = {
111
+ :captures => 'Mock',
112
+ :expects => 'Expects',
113
+ :idempotent => 'Idempotent',
114
+ :instrumentor_name => 'Instrumentor',
115
+ :mock => 'Mock',
116
+ :retries_remaining => 'Idempotent', # referenced in Instrumentor, but only relevant with Idempotent
117
+ :retry_errors => 'Idempotent',
118
+ :retry_interval => 'Idempotent',
119
+ :retry_limit => 'Idempotent' # referenced in Instrumentor, but only relevant with Idempotent
120
+ }
121
+
100
122
  unless ::IO.const_defined?(:WaitReadable)
101
123
  class ::IO
102
124
  module WaitReadable; end
@@ -112,12 +134,14 @@ module Excon
112
134
  DEFAULTS = {
113
135
  :chunk_size => CHUNK_SIZE || DEFAULT_CHUNK_SIZE,
114
136
  # 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',
137
+ # list provided then had DES related things sorted to the end
138
+ :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
139
  :connect_timeout => 60,
117
140
  :debug_request => false,
118
141
  :debug_response => false,
119
142
  :headers => {
120
- 'User-Agent' => USER_AGENT
143
+ 'User-Agent' => USER_AGENT,
144
+ 'Accept' => '*/*'
121
145
  },
122
146
  :idempotent => false,
123
147
  :instrumentor_name => 'excon',
@@ -133,6 +157,7 @@ module Excon
133
157
  :omit_default_port => false,
134
158
  :persistent => false,
135
159
  :read_timeout => 60,
160
+ :retry_errors => DEFAULT_RETRY_ERRORS,
136
161
  :retry_limit => DEFAULT_RETRY_LIMIT,
137
162
  :ssl_verify_peer => true,
138
163
  :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,9 +46,23 @@ 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
53
+
54
+ class ProxyConnectionError < Error
55
+ attr_reader :request, :response
56
+
57
+ def initialize(msg, request = nil, response = nil)
58
+ super(msg)
59
+ @request = request
60
+ @response = response
61
+ end
62
+ end
63
+
50
64
  class ProxyParse < Error; end
65
+ class TooManyRedirects < Error; end
51
66
 
52
67
  # Base class for HTTP Error classes
53
68
  class HTTPStatus < Error
data/lib/excon/headers.rb CHANGED
@@ -22,6 +22,7 @@ module Excon
22
22
  alias_method :raw_values_at, :values_at
23
23
 
24
24
  def initialize
25
+ super
25
26
  @downcased = {}
26
27
  end
27
28
 
@@ -29,11 +30,11 @@ module Excon
29
30
  @downcased[key.to_s.downcase]
30
31
  end
31
32
 
32
- alias_method :[]=, :store
33
33
  def []=(key, value)
34
34
  raw_writer(key, value)
35
35
  @downcased[key.to_s.downcase] = value
36
36
  end
37
+ alias_method :store, :[]=
37
38
 
38
39
  if SENTINEL.respond_to? :assoc
39
40
  def assoc(obj)
@@ -54,11 +55,11 @@ module Excon
54
55
  end
55
56
  end
56
57
 
57
- alias_method :has_key?, :key?
58
- alias_method :has_key?, :member?
59
58
  def has_key?(key)
60
59
  raw_key?(key) || @downcased.has_key?(key.to_s.downcase)
61
60
  end
61
+ alias_method :key?, :has_key?
62
+ alias_method :member?, :has_key?
62
63
 
63
64
  def merge(other_hash)
64
65
  self.dup.merge!(other_hash)
@@ -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
- def self.instrument(name, params = {}, &block)
6
+ def self.instrument(name, params = {})
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)
@@ -39,7 +28,7 @@ module Excon
39
28
  info << "?"
40
29
 
41
30
  if params[:query].is_a?(Hash)
42
- info << params.to_a.map{ |key,value| "#{key}=#{value}" }.join('&')
31
+ info << params[:query].to_a.map { |key,value| "#{key}=#{value}" }.join('&')
43
32
  else
44
33
  info << params[:query]
45
34
  end
@@ -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.info(info) if info
55
44
 
56
45
  yield if block_given?
57
46
  end
@@ -1,21 +1,14 @@
1
1
  # frozen_string_literal: true
2
2
  module Excon
3
3
  class StandardInstrumentor
4
- def self.instrument(name, params = {}, &block)
4
+ def self.instrument(name, params = {})
5
5
  params = params.dup
6
6
 
7
7
  # reduce duplication/noise of output
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
@@ -8,7 +8,7 @@ module Excon
8
8
  end
9
9
 
10
10
  def get_header(datum, header)
11
- _, header_value = datum[:response][:headers].detect do |key, value|
11
+ _, header_value = datum[:response][:headers].detect do |key, _|
12
12
  key.casecmp(header) == 0
13
13
  end
14
14
  header_value
@@ -2,6 +2,10 @@
2
2
  module Excon
3
3
  module Middleware
4
4
  class Decompress < Excon::Middleware::Base
5
+
6
+ INFLATE_ZLIB_OR_GZIP = 47 # Zlib::MAX_WBITS + 32
7
+ INFLATE_RAW = -15 # Zlib::MAX_WBITS * -1
8
+
5
9
  def request_call(datum)
6
10
  unless datum.has_key?(:response_block)
7
11
  key = datum[:headers].keys.detect {|k| k.to_s.casecmp('Accept-Encoding') == 0 } || 'Accept-Encoding'
@@ -15,12 +19,15 @@ module Excon
15
19
  def response_call(datum)
16
20
  body = datum[:response][:body]
17
21
  unless datum.has_key?(:response_block) || body.nil? || body.empty?
18
- if key = datum[:response][:headers].keys.detect {|k| k.casecmp('Content-Encoding') == 0 }
22
+ if (key = datum[:response][:headers].keys.detect {|k| k.casecmp('Content-Encoding') == 0 })
19
23
  encodings = Utils.split_header_value(datum[:response][:headers][key])
20
- if encoding = encodings.last
24
+ if (encoding = encodings.last)
21
25
  if encoding.casecmp('deflate') == 0
22
- # assume inflate omits header
23
- datum[:response][:body] = Zlib::Inflate.new(-Zlib::MAX_WBITS).inflate(body)
26
+ datum[:response][:body] = begin
27
+ Zlib::Inflate.new(INFLATE_ZLIB_OR_GZIP).inflate(body)
28
+ rescue Zlib::DataError # fallback to raw on error
29
+ Zlib::Inflate.new(INFLATE_RAW).inflate(body)
30
+ end
24
31
  encodings.pop
25
32
  elsif encoding.casecmp('gzip') == 0 || encoding.casecmp('x-gzip') == 0
26
33
  datum[:response][:body] = Zlib::GzipReader.new(StringIO.new(body)).read
@@ -2,11 +2,17 @@
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(
8
14
  Excon::Errors.status_error(
9
- datum.reject {|key,value| key == :response},
15
+ datum.reject {|key,_| key == :response},
10
16
  Excon::Response.new(datum[:response])
11
17
  )
12
18
  )
@@ -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:
@@ -17,12 +25,13 @@ module Excon
17
25
  raise Excon::Errors::InvalidStub.new("Request body should be a string or an IO object. #{datum[:body].class} provided")
18
26
  end
19
27
 
20
- if stub = Excon.stub_for(datum)
28
+ if (stub = Excon.stub_for(datum))
29
+ datum[:remote_ip] ||= '127.0.0.1'
21
30
  datum[:response] = {
22
31
  :body => '',
23
32
  :headers => {},
24
33
  :status => 200,
25
- :remote_ip => '127.0.0.1'
34
+ :remote_ip => datum[:remote_ip]
26
35
  }
27
36
 
28
37
  stub_datum = case stub.last
@@ -32,7 +41,7 @@ module Excon
32
41
  stub.last
33
42
  end
34
43
 
35
- datum[:response].merge!(stub_datum.reject {|key,value| key == :headers})
44
+ datum[:response].merge!(stub_datum.reject {|key,_| key == :headers})
36
45
  if stub_datum.has_key?(:headers)
37
46
  datum[:response][:headers].merge!(stub_datum[:headers])
38
47
  end
@@ -2,9 +2,21 @@
2
2
  module Excon
3
3
  module Middleware
4
4
  class RedirectFollower < Excon::Middleware::Base
5
+ def self.valid_parameter_keys
6
+ [
7
+ :redirects_remaining,
8
+ :redirect_limit
9
+ ]
10
+ end
11
+
12
+ def request_call(datum)
13
+ datum[:redirects_remaining] ||= datum[:redirect_limit] ||
14
+ Excon::DEFAULT_REDIRECT_LIMIT
15
+ @stack.request_call(datum)
16
+ end
5
17
 
6
18
  def get_header(datum, header)
7
- _, header_value = datum[:response][:headers].detect do |key, value|
19
+ _, header_value = datum[:response][:headers].detect do |key, _|
8
20
  key.casecmp(header) == 0
9
21
  end
10
22
  header_value
@@ -14,14 +26,21 @@ module Excon
14
26
  if datum.has_key?(:response)
15
27
  case datum[:response][:status]
16
28
  when 301, 302, 303, 307, 308
29
+ if datum[:redirects_remaining] <= 0
30
+ raise Excon::Errors::TooManyRedirects
31
+ end
32
+
33
+ datum[:redirects_remaining] -= 1
34
+
17
35
  uri_parser = datum[:uri_parser] || Excon.defaults[:uri_parser]
18
36
 
19
37
  location = get_header(datum, 'Location')
20
38
  base_uri = Excon::Utils.request_uri(datum)
21
39
  uri = uri_parser.join(base_uri, location)
22
40
 
23
- # delete old/redirect response
41
+ # delete old/redirect response and remote_ip
24
42
  response = datum.delete(:response)
43
+ datum.delete(:remote_ip)
25
44
 
26
45
  params = datum.dup
27
46
  params.delete(:connection)
@@ -52,7 +71,10 @@ module Excon
52
71
  params.merge!(:password => Utils.unescape_uri(uri.password)) if uri.password
53
72
 
54
73
  response = Excon::Connection.new(params).request
55
- datum.merge!({:response => response.data})
74
+ datum.merge!({
75
+ :remote_ip => response.remote_ip,
76
+ :response => response.data
77
+ })
56
78
  else
57
79
  @stack.response_call(datum)
58
80
  end
@@ -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
@@ -59,10 +59,13 @@ module Excon
59
59
 
60
60
  def self.parse(socket, datum)
61
61
  # this will discard any trailing lines from the previous response if any.
62
- begin
62
+ line = nil
63
+ loop do
63
64
  line = socket.readline
64
- end until status = line[9, 3].to_i
65
+ break if line[9,3].to_i != 0
66
+ end
65
67
 
68
+ status = line[9, 3].to_i
66
69
  reason_phrase = line[13..-3] # -3 strips the trailing "\r\n"
67
70
 
68
71
  datum[:response] = {
@@ -90,7 +93,7 @@ module Excon
90
93
 
91
94
  unless (['HEAD', 'CONNECT'].include?(datum[:method].to_s.upcase)) || NO_ENTITY.include?(datum[:response][:status])
92
95
 
93
- if key = datum[:response][:headers].keys.detect {|k| k.casecmp('Transfer-Encoding') == 0 }
96
+ if (key = datum[:response][:headers].keys.detect {|k| k.casecmp('Transfer-Encoding') == 0 })
94
97
  encodings = Utils.split_header_value(datum[:response][:headers][key])
95
98
  if (encoding = encodings.last) && encoding.casecmp('chunked') == 0
96
99
  transfer_encoding_chunked = true
@@ -103,7 +106,7 @@ module Excon
103
106
  end
104
107
 
105
108
  # use :response_block unless :expects would fail
106
- if response_block = datum[:response_block]
109
+ if (response_block = datum[:response_block])
107
110
  if datum[:middlewares].include?(Excon::Middleware::Expects) && datum[:expects] &&
108
111
  !Array(datum[:expects]).include?(datum[:response][:status])
109
112
  response_block = nil
@@ -140,11 +143,11 @@ module Excon
140
143
  end
141
144
  parse_headers(socket, datum) # merge trailers into headers
142
145
  else
143
- if key = datum[:response][:headers].keys.detect {|k| k.casecmp('Content-Length') == 0 }
146
+ if (key = datum[:response][:headers].keys.detect {|k| k.casecmp('Content-Length') == 0 })
144
147
  content_length = datum[:response][:headers][key].to_i
145
148
  end
146
149
 
147
- if remaining = content_length
150
+ if (remaining = content_length)
148
151
  if response_block
149
152
  while remaining > 0
150
153
  chunk = socket.read([datum[:chunk_size], remaining].min) || raise(EOFError)
@@ -160,11 +163,11 @@ module Excon
160
163
  end
161
164
  else
162
165
  if response_block
163
- while chunk = socket.read(datum[:chunk_size])
166
+ while (chunk = socket.read(datum[:chunk_size]))
164
167
  response_block.call(chunk, nil, nil)
165
168
  end
166
169
  else
167
- while chunk = socket.read(datum[:chunk_size])
170
+ while (chunk = socket.read(datum[:chunk_size]))
168
171
  datum[:response][:body] << chunk
169
172
  end
170
173
  end
@@ -222,7 +225,7 @@ module Excon
222
225
  end
223
226
 
224
227
  # Retrieve a specific header value. Header names are treated case-insensitively.
225
- # @param [String] name Header name
228
+ # @param [String] name Header name
226
229
  def get_header(name)
227
230
  headers[name]
228
231
  end